diff --git a/.ci.yaml b/.ci.yaml index ef9d698fead..78b9751b533 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -65,7 +65,7 @@ platform_properties: device_type: none dependencies: >- [ - {"dependency": "chrome_and_driver", "version": "version:114.0"} + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] windows_arm64: properties: @@ -90,7 +90,7 @@ platform_properties: [ {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} ] - os: Mac-13 + os: Mac-13|Mac-14 device_type: none cpu: arm64 $flutter/osx_sdk : >- @@ -103,7 +103,7 @@ platform_properties: [ {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} ] - os: Mac-13 + os: Mac-13|Mac-14 device_type: none cpu: x86 $flutter/osx_sdk : >- @@ -150,7 +150,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" target_file: dart_unit_tests.yaml channel: master version_file: flutter_master.version @@ -208,7 +207,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" target_file: web_dart_unit_tests.yaml channel: master version_file: flutter_master.version @@ -251,7 +249,6 @@ targets: recipe: packages/packages timeout: 30 properties: - add_recipes_cq: "true" target_file: analyze.yaml channel: master version_file: flutter_master.version @@ -295,10 +292,10 @@ targets: timeout: 30 properties: target_file: analyze_legacy.yaml - channel: "3.16.9" + channel: "3.22.0" env_variables: >- { - "CHANNEL": "3.16.9" + "CHANNEL": "3.22.0" } - name: Linux analyze_legacy N-2 @@ -306,10 +303,10 @@ targets: timeout: 30 properties: target_file: analyze_legacy.yaml - channel: "3.13.9" + channel: "3.16.9" env_variables: >- { - "CHANNEL": "3.13.9" + "CHANNEL": "3.16.9" } - name: Linux_android custom_package_tests master @@ -330,7 +327,7 @@ targets: {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, {"dependency": "ninja", "version": "version:1.9.0"}, - {"dependency": "chrome_and_driver", "version": "version:114.0"} + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] channel: master env_variables: >- @@ -353,7 +350,7 @@ targets: {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, {"dependency": "ninja", "version": "version:1.9.0"}, - {"dependency": "chrome_and_driver", "version": "version:114.0"} + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] channel: stable env_variables: >- @@ -366,7 +363,6 @@ targets: recipe: packages/packages timeout: 30 properties: - add_recipes_cq: "true" version_file: flutter_master.version target_file: android_build_all_packages.yaml channel: master @@ -398,9 +394,6 @@ targets: "CHANNEL": "stable" } - # 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 @@ -408,7 +401,6 @@ 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: >- [ @@ -427,7 +419,6 @@ 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: >- [ @@ -446,7 +437,6 @@ 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: >- [ @@ -465,7 +455,6 @@ 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: >- [ @@ -484,7 +473,6 @@ 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: >- [ @@ -503,7 +491,6 @@ 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: >- [ @@ -515,44 +502,6 @@ targets: "PACKAGE_SHARDING": "--shardIndex 5 --shardCount 6" } - - name: Linux_android android_platform_tests_api_33_shard_1 master - recipe: packages/packages - timeout: 60 - properties: - 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: >- - [ - {"dependency": "android_virtual_device", "version": "android_33_google_apis_x64.textpb"} - ] - env_variables: >- - { - "CHANNEL": "master", - "PACKAGE_SHARDING": "--shardIndex 0 --shardCount 2" - } - - - name: Linux_android android_platform_tests_api_33_shard_2 master - recipe: packages/packages - timeout: 60 - properties: - 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: >- - [ - {"dependency": "android_virtual_device", "version": "android_33_google_apis_x64.textpb"} - ] - env_variables: >- - { - "CHANNEL": "master", - "PACKAGE_SHARDING": "--shardIndex 1 --shardCount 2" - } - - name: Linux_android android_platform_tests_shard_1 stable recipe: packages/packages presubmit: false @@ -667,42 +616,6 @@ targets: "PACKAGE_SHARDING": "--shardIndex 5 --shardCount 6" } - - name: Linux_android android_platform_tests_api_33_shard_1 stable - recipe: packages/packages - timeout: 60 - properties: - target_file: android_platform_tests_api_33_avd.yaml - channel: stable - version_file: flutter_stable.version - package_sharding: "--shardIndex 0 --shardCount 2" - dependencies: >- - [ - {"dependency": "android_virtual_device", "version": "android_33_google_apis_x64.textpb"} - ] - env_variables: >- - { - "CHANNEL": "stable", - "PACKAGE_SHARDING": "--shardIndex 0 --shardCount 2" - } - - - name: Linux_android android_platform_tests_api_33_shard_2 stable - recipe: packages/packages - timeout: 60 - properties: - target_file: android_platform_tests_api_33_avd.yaml - channel: stable - version_file: flutter_stable.version - package_sharding: "--shardIndex 1 --shardCount 2" - dependencies: >- - [ - {"dependency": "android_virtual_device", "version": "android_33_google_apis_x64.textpb"} - ] - env_variables: >- - { - "CHANNEL": "stable", - "PACKAGE_SHARDING": "--shardIndex 1 --shardCount 2" - } - - name: Linux_android_legacy android_platform_tests_legacy_api_shard_1 master recipe: packages/packages timeout: 60 @@ -1022,7 +935,7 @@ targets: # Install Chrome as a default handler for schemes for url_launcher. dependencies: >- [ - {"dependency": "chrome_and_driver", "version": "version:114.0"} + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] env_variables: >- { @@ -1040,7 +953,7 @@ targets: # Install Chrome as a default handler for schemes for url_launcher. dependencies: >- [ - {"dependency": "chrome_and_driver", "version": "version:114.0"} + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] env_variables: >- { @@ -1048,19 +961,15 @@ targets: } ### iOS+macOS tasks ### - # TODO(stuartmorgan): Move this to ARM once google_maps_flutter has ARM - # support. `pod lint` makes a synthetic target that doesn't respect the - # pod's arch exclusions, so fails to build. - - name: Mac_x64 check_podspecs + - name: Mac_arm64 macos_repo_checks recipe: packages/packages timeout: 30 properties: - add_recipes_cq: "true" version_file: flutter_master.version target_file: macos_repo_checks.yaml dependencies: > [ - {"dependency": "swift_format", "version": "build_id:8797338980206841409"} + {"dependency": "swift_format", "version": "build_id:8797338979890974865"} ] ### macOS desktop tasks ### @@ -1070,7 +979,6 @@ targets: recipe: packages/packages timeout: 30 properties: - add_recipes_cq: "true" version_file: flutter_master.version target_file: macos_build_all_packages.yaml channel: master @@ -1125,7 +1033,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" version_file: flutter_master.version target_file: macos_custom_package_tests.yaml channel: master @@ -1142,7 +1049,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" version_file: flutter_stable.version target_file: macos_custom_package_tests.yaml channel: stable @@ -1163,7 +1069,6 @@ targets: timeout: 30 properties: channel: master - add_recipes_cq: "true" version_file: flutter_master.version target_file: ios_build_all_packages.yaml env_variables: >- @@ -1176,7 +1081,6 @@ targets: timeout: 30 properties: channel: stable - add_recipes_cq: "true" version_file: flutter_stable.version target_file: ios_build_all_packages.yaml env_variables: >- @@ -1204,7 +1108,6 @@ targets: timeout: 60 properties: channel: master - add_recipes_cq: "true" version_file: flutter_master.version target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 1 --shardCount 5" @@ -1219,7 +1122,6 @@ targets: timeout: 60 properties: channel: master - add_recipes_cq: "true" version_file: flutter_master.version target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 2 --shardCount 5" @@ -1234,7 +1136,6 @@ targets: timeout: 60 properties: channel: master - add_recipes_cq: "true" version_file: flutter_master.version target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 5" @@ -1249,7 +1150,6 @@ targets: timeout: 60 properties: channel: master - add_recipes_cq: "true" version_file: flutter_master.version target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 4 --shardCount 5" @@ -1340,7 +1240,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" target_file: windows_custom_package_tests.yaml channel: master version_file: flutter_master.version @@ -1404,7 +1303,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml channel: master version_file: flutter_master.version @@ -1423,7 +1321,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml channel: stable version_file: flutter_stable.version @@ -1442,7 +1339,6 @@ targets: recipe: packages/packages timeout: 60 properties: - add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml channel: stable version_file: flutter_stable.version @@ -1461,7 +1357,6 @@ targets: recipe: packages/packages timeout: 30 properties: - add_recipes_cq: "true" target_file: windows_build_all_packages.yaml channel: master version_file: flutter_master.version @@ -1515,7 +1410,6 @@ targets: timeout: 30 bringup: true properties: - add_recipes_cq: "true" target_file: windows_build_all_packages.yaml channel: stable version_file: flutter_stable.version diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index b0977b2d26d..02c8bcb4c2e 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -fb110b98da1588c775c0b42b4a138c04fda496ca +99bb2ff6a61428036323bb8734e92dad0daf950e diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index 304f1de6baf..60e33a4667d 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -300451adae589accbece3490f4396f10bdf15e6e +761747bfc538b5af34aa0d3fac380f1bc331ec49 diff --git a/.ci/legacy_project/README.md b/.ci/legacy_project/README.md index 0e23f626fb4..1ee7313e73d 100644 --- a/.ci/legacy_project/README.md +++ b/.ci/legacy_project/README.md @@ -42,3 +42,11 @@ and then deleting everything but `android/` from it: - Updates `gradle-wrapper.properties` from `6.7` to `6.7.1`, to add support for the Kotlin gradle plugin. If a user runs into this error, the error message is clear on how to upgrade. +- Modifies `build.gradle` to upgrade the Android Gradle Plugin (AGP) + from version 4.1.0 to 7.0.0. If a user runs into an error with + the AGP version, the warning is clear on how to upgrade + the version to one that we support. +- Modifies `gradle-wrapper.properties` to upgrade the Gradle version + from 6.7.1 to 7.0.2. If a user runs into an error with the Gradle + version, the warning is clear on how to upgrade the version to + one that we support. \ No newline at end of file diff --git a/.ci/legacy_project/all_packages/android/.gitignore b/.ci/legacy_project/all_packages/android/.gitignore index 0a741cb43d6..8e599af9f21 100644 --- a/.ci/legacy_project/all_packages/android/.gitignore +++ b/.ci/legacy_project/all_packages/android/.gitignore @@ -7,5 +7,5 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties diff --git a/.ci/legacy_project/all_packages/android/build.gradle b/.ci/legacy_project/all_packages/android/build.gradle index 0b4cf534e0a..08cb0aa3de9 100644 --- a/.ci/legacy_project/all_packages/android/build.gradle +++ b/.ci/legacy_project/all_packages/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.0.0' } } diff --git a/.ci/legacy_project/all_packages/android/gradle/wrapper/gradle-wrapper.properties b/.ci/legacy_project/all_packages/android/gradle/wrapper/gradle-wrapper.properties index 939efa2951b..b8793d3c0d6 100644 --- a/.ci/legacy_project/all_packages/android/gradle/wrapper/gradle-wrapper.properties +++ b/.ci/legacy_project/all_packages/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/.ci/scripts/create_all_packages_app.sh b/.ci/scripts/create_all_packages_app.sh index 4440dfe2608..a0ff3ad89d4 100755 --- a/.ci/scripts/create_all_packages_app.sh +++ b/.ci/scripts/create_all_packages_app.sh @@ -4,5 +4,16 @@ # found in the LICENSE file. set -e +# The base exclusion file for all_packages app. +exclusions=("script/configs/exclude_all_packages_app.yaml") + +# Add a wasm-specific exclusion file if "--wasm" is specified. +if [[ "$1" == "--wasm" ]]; then + exclusions+=",script/configs/exclude_all_packages_app_wasm.yaml" +fi + +# Delete ./all_packages if it exists already +rm -rf ./all_packages + dart ./script/tool/bin/flutter_plugin_tools.dart create-all-packages-app \ - --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml + --output-dir=. --exclude "$exclusions" diff --git a/.ci/targets/analyze_legacy.yaml b/.ci/targets/analyze_legacy.yaml index 0d2d3335846..e8e48a70253 100644 --- a/.ci/targets/analyze_legacy.yaml +++ b/.ci/targets/analyze_legacy.yaml @@ -7,6 +7,6 @@ tasks: # This is to minimize accidentally making changes that break old versions # (which we don't commit to supporting, but don't want to actively break) # without updating the constraints. - # See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#supported-flutter-versions + # See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#supported-flutter-versions - name: analyze - legacy script: .ci/scripts/analyze_legacy.sh diff --git a/.ci/targets/android_platform_tests.yaml b/.ci/targets/android_platform_tests.yaml index 23fc6039bdf..d1019f7df18 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: .ci/scripts/tool_runner.sh infra_step: true - args: ["fetch-deps", "--android", "--supporting-target-platforms-only", "--exclude=script/configs/still_requires_api_33_avd.yaml"] + args: ["fetch-deps", "--android", "--supporting-target-platforms-only"] - name: build examples script: .ci/scripts/tool_runner.sh - args: ["build-examples", "--apk", "--exclude=script/configs/still_requires_api_33_avd.yaml"] + args: ["build-examples", "--apk"] - name: lint script: .ci/scripts/tool_runner.sh - args: ["lint-android", "--exclude=script/configs/still_requires_api_33_avd.yaml"] + args: ["lint-android"] # 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: .ci/scripts/tool_runner.sh - args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml,script/configs/still_requires_api_33_avd.yaml"] + args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml"] - name: native integration tests script: .ci/scripts/tool_runner.sh - args: ["native-test", "--android", "--no-unit", "--exclude=script/configs/still_requires_api_33_avd.yaml"] + args: ["native-test", "--android", "--no-unit"] - name: drive examples script: .ci/scripts/tool_runner.sh - 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"] + args: ["drive-examples", "--android", "--exclude=script/configs/exclude_integration_android.yaml,script/configs/exclude_integration_android_emulator.yaml"] diff --git a/.ci/targets/android_platform_tests_api_33_avd.yaml b/.ci/targets/android_platform_tests_api_33_avd.yaml deleted file mode 100644 index cc5324b141f..00000000000 --- a/.ci/targets/android_platform_tests_api_33_avd.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Same as android_platform_tests.yaml with only packages currently requiring -# Android 33 due to test failures caused by running on Android 34 AVDs. -tasks: - - name: prepare tool - script: .ci/scripts/prepare_tool.sh - infra_step: true # Note infra steps failing prevents "always" from running. - - name: download Dart and Android deps - script: .ci/scripts/tool_runner.sh - infra_step: true - args: ["fetch-deps", "--android", "--supporting-target-platforms-only", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] - - name: build examples - script: .ci/scripts/tool_runner.sh - args: ["build-examples", "--apk", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] - - name: lint - script: .ci/scripts/tool_runner.sh - args: ["lint-android", "--filter-packages-to=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: .ci/scripts/tool_runner.sh - 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: .ci/scripts/tool_runner.sh - args: ["native-test", "--android", "--no-unit", "--filter-packages-to=script/configs/still_requires_api_33_avd.yaml"] - - name: drive examples - script: .ci/scripts/tool_runner.sh - 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/.ci/targets/ios_platform_tests.yaml b/.ci/targets/ios_platform_tests.yaml index b961b675d6b..e679834961f 100644 --- a/.ci/targets/ios_platform_tests.yaml +++ b/.ci/targets/ios_platform_tests.yaml @@ -11,7 +11,7 @@ tasks: infra_step: true - name: build examples script: .ci/scripts/tool_runner.sh - args: ["build-examples", "--ios"] + args: ["build-examples", "--ios", "--swift-package-manager"] - name: xcode analyze script: .ci/scripts/tool_runner.sh args: ["xcode-analyze", "--ios"] diff --git a/.ci/targets/macos_platform_tests.yaml b/.ci/targets/macos_platform_tests.yaml index bc1b915e561..1dd105f109f 100644 --- a/.ci/targets/macos_platform_tests.yaml +++ b/.ci/targets/macos_platform_tests.yaml @@ -8,7 +8,7 @@ tasks: infra_step: true - name: build examples script: .ci/scripts/tool_runner.sh - args: ["build-examples", "--macos"] + args: ["build-examples", "--macos", "--swift-package-manager"] - name: xcode analyze script: .ci/scripts/tool_runner.sh args: ["xcode-analyze", "--macos"] diff --git a/.ci/targets/repo_checks.yaml b/.ci/targets/repo_checks.yaml index 8e22ffd6ea5..a63240a4ebe 100644 --- a/.ci/targets/repo_checks.yaml +++ b/.ci/targets/repo_checks.yaml @@ -19,7 +19,7 @@ tasks: script: .ci/scripts/tool_runner.sh args: - "pubspec-check" - - "--min-min-flutter-version=3.13.0" + - "--min-min-flutter-version=3.16.0" - "--allow-dependencies=script/configs/allowed_unpinned_deps.yaml" - "--allow-pinned-dependencies=script/configs/allowed_pinned_deps.yaml" always: true diff --git a/.ci/targets/web_build_all_packages.yaml b/.ci/targets/web_build_all_packages.yaml index 1be790f62d5..bb782c560d2 100644 --- a/.ci/targets/web_build_all_packages.yaml +++ b/.ci/targets/web_build_all_packages.yaml @@ -10,3 +10,10 @@ tasks: - name: build all_packages app for Web release script: .ci/scripts/build_all_packages_app.sh args: ["web", "release"] + - name: (Wasm) create all_packages app + script: .ci/scripts/create_all_packages_app.sh + args: ["--wasm"] + infra_step: true # Note infra steps failing prevents "always" from running. + - name: (Wasm) build all_packages app for Web release + script: .ci/scripts/build_all_packages_app.sh + args: ["web", "release", "--wasm"] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 09aaa6b43ef..e2ad38b34d3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,18 +2,16 @@ *List which issues are fixed by this PR. You must list at least one issue.* -*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* - ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. -- [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. +- [ ] I read the [Tree Hygiene] page, which explains my responsibilities. - [ ] I read and followed the [relevant style guides] and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use `dart format`.) - [ ] I signed the [CLA]. - [ ] The title of the PR starts with the name of the package surrounded by square brackets, e.g. `[shared_preferences]` - [ ] I [linked to at least one issue that this PR fixes] in the description above. - [ ] I updated `pubspec.yaml` with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes]. -- [ ] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style]. +- [ ] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style], or this PR is [exempt from CHANGELOG changes]. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] All existing and new tests are passing. @@ -22,14 +20,13 @@ If you need help, consider asking for advice on the #hackers-new channel on [Dis [Contributor Guide]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md -[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene +[Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md [relevant style guides]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style [CLA]: https://cla.developers.google.com/ -[flutter/tests]: https://github.com/flutter/tests -[breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes -[Discord]: https://github.com/flutter/flutter/wiki/Chat -[linked to at least one issue that this PR fixes]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview +[Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md +[linked to at least one issue that this PR fixes]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview [pub versioning philosophy]: https://dart.dev/tools/pub/versioning -[exempt from version changes]: https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#version-and-changelog-updates -[following repository CHANGELOG style]: https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changelog-style -[test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests +[exempt from version changes]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#version +[following repository CHANGELOG style]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog-style +[exempt from CHANGELOG changes]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changelog +[test-exempt]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d1f55572e69..9681048365d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -107,17 +107,6 @@ updates: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - - package-ecosystem: "gradle" - directory: "/packages/dynamic_layouts/example/android/app" - commit-message: - prefix: "[dynamic_lyts]" - schedule: - interval: "weekly" - open-pull-requests-limit: 10 - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-minor", "version-update:semver-patch"] - - package-ecosystem: "gradle" directory: "/packages/espresso/android" commit-message: @@ -186,6 +175,8 @@ updates: update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - dependency-name: "org.robolectric:*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/file_selector/file_selector_android/example/android/app" diff --git a/.github/labeler.yml b/.github/labeler.yml index a967a52b74c..d44cb1298ec 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -23,11 +23,6 @@ - any-glob-to-any-file: - third_party/packages/cupertino_icons/**/* -'p: dynamic_layouts': - - changed-files: - - any-glob-to-any-file: - - packages/dynamic_layouts/**/* - 'p: espresso': - changed-files: - any-glob-to-any-file: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a72b9342c67..341585fb967 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,12 +31,12 @@ jobs: # the change if it doesn't. run: | cd $HOME - git clone https://github.com/flutter/flutter.git --depth 1 -b 3.19.0 _flutter + git clone https://github.com/flutter/flutter.git --depth 1 -b 3.22.0 _flutter echo "$HOME/_flutter/bin" >> $GITHUB_PATH cd $GITHUB_WORKSPACE # Checks out a copy of the repo. - name: Check out code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 with: fetch-depth: 0 # Fetch all history so the tool can get all the tags to determine version. - name: Set up tools diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index c67a027a678..395418d7e52 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -21,12 +21,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v2.4.0 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v2.4.0 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.0.3 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.0.3 with: results_file: results.sarif results_format: sarif @@ -41,7 +41,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v2.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v2.3.1 with: name: SARIF file path: results.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@c7f9125735019aa87cfc361530512d50ea439c71 # v1.0.26 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v1.0.26 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 1c37b779a46..0af22121e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,11 @@ pubspec.lock +# iOS and macOS dependencies +.build/ Podfile.lock Pods/ +.swiftpm/ .symlinks/ *instrumentscli*.trace diff --git a/AUTHORS b/AUTHORS index 543b9813fe2..6ea7eeec4f5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -74,4 +74,6 @@ Twin Sun, LLC Amir Panahandeh Daniele Cambi Michele Benedetti -Taskulu LDA \ No newline at end of file +Taskulu LDA +LinXunFeng +Hashir Shoaib diff --git a/CODEOWNERS b/CODEOWNERS index eeb05655cf0..d7c37efa2fb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,7 +8,6 @@ packages/animations/** @goderbauer packages/camera/** @bparrishMines packages/cross_file/** @ditman packages/css_colors/** @stuartmorgan -packages/dynamic_layouts/** @Piinks packages/extension_google_sign_in_as_googleapis_auth/** @ditman packages/file_selector/** @stuartmorgan packages/flutter_adaptive_scaffold/** @gspencergoog @@ -26,7 +25,7 @@ packages/image_picker/** @tarrinneal packages/interactive_media_ads/** @bparrishMines packages/in_app_purchase/** @bparrishMines packages/local_auth/** @stuartmorgan -packages/metrics_center/** @keyonghan +packages/metrics_center/** @christopherfujino packages/multicast_dns/** @jmagman packages/palette_generator/** @gspencergoog packages/path_provider/** @stuartmorgan @@ -63,8 +62,9 @@ packages/video_player/video_player_web/** @ditman packages/webview_flutter/webview_flutter_web/** @ditman # - Android -packages/camera/camera_android/** @camsim99 -packages/camera/camera_android_camerax/** @camsim99 +# TODO(matanlurey): Remove @matanlurey after https://github.com/flutter/flutter/issues/151018. +packages/camera/camera_android/** @camsim99 @matanlurey +packages/camera/camera_android_camerax/** @camsim99 @matanlurey packages/espresso/** @reidbaker packages/file_selector/file_selector_android/** @gmackall packages/flutter_plugin_android_lifecycle/** @reidbaker @@ -77,7 +77,7 @@ packages/path_provider/path_provider_android/** @camsim99 packages/quick_actions/quick_actions_android/** @camsim99 packages/shared_preferences/shared_preferences_android/** @reidbaker packages/url_launcher/url_launcher_android/** @gmackall -packages/video_player/video_player_android/** @camsim99 +packages/video_player/video_player_android/** @camsim99 @matanlurey # Owned by ecosystem team for now during the wrapper evaluation. packages/webview_flutter/webview_flutter_android/** @bparrishMines @@ -86,8 +86,8 @@ packages/camera/camera_avfoundation/** @hellohuanlin packages/file_selector/file_selector_ios/** @jmagman packages/file_selector/file_selector_macos/** @jmagman packages/google_maps_flutter/google_maps_flutter_ios/** @cbracken -packages/google_sign_in/google_sign_in_ios/** @vashworth @loic-sharma -packages/image_picker/image_picker_ios/** @vashworth @loic-sharma +packages/google_sign_in/google_sign_in_ios/** @loic-sharma +packages/image_picker/image_picker_ios/** @loic-sharma packages/image_picker/image_picker_macos/** @jmagman packages/in_app_purchase/in_app_purchase_storekit/** @louisehsu packages/ios_platform_images/** @jmagman diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd3fb8bf6a5..8801cc41286 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,17 +5,17 @@ guide](https://github.com/flutter/flutter/blob/master/CONTRIBUTING.md). Additional resources specific to the packages repository: - [Setting up the Packages development - environment](https://github.com/flutter/flutter/wiki/Setting-up-the-Packages-development-environment), + environment](https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/Setting-up-the-Packages-development-environment.md), which covers the setup process for this repository. -- [Packages repository structure](https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure), +- [Packages repository structure](https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md), to get an overview of how this repository is laid out. -- [Contributing to Plugins and Packages](https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages), +- [Contributing to Plugins and Packages](https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md), for more information about how to make PRs for this repository, especially when changing federated plugins. -- [Plugin tests](https://github.com/flutter/flutter/wiki/Plugin-Tests), which explains - the different kinds of tests used for plugins, where to find them, and how to run them. +- [Plugin tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md), + which explains the different kinds of tests used for plugins, where to find them, and how to run them. As explained in the Flutter guide, - [**PRs need tests**](https://github.com/flutter/flutter/wiki/Tree-hygiene#tests), so + [**PRs need tests**](https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests), so this is critical to read before submitting a plugin PR. ### Code review processes and automation @@ -24,14 +24,14 @@ PRs will automatically be assigned to [code owners](https://github.com/flutter/packages/blob/main/CODEOWNERS) for review. If a code owner is creating a PR, they should explicitly pick another -[Flutter team member](https://github.com/flutter/flutter/wiki/Contributor-access) +[Flutter team member](https://github.com/flutter/flutter/blob/master/docs/contributing/Contributor-access.md) as a code reviewer. ### Style Flutter packages and plugins follow Google style—or Flutter style for Dart—for the languages they use, and use auto-formatters: -- [Dart](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo) formatted +- [Dart](https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md) formatted with `dart format` - [C++](https://google.github.io/styleguide/cppguide.html) formatted with `clang-format` - **Note**: The Linux plugins generally follow idiomatic GObject-based C @@ -50,4 +50,4 @@ use, and use auto-formatters: If you are a team member landing a PR, or just want to know what the release process is for package changes, see [the release -documentation](https://github.com/flutter/flutter/wiki/Releasing-a-Plugin-or-Package). +documentation](https://github.com/flutter/flutter/blob/master/docs/ecosystem/release/README.md). diff --git a/README.md b/README.md index 1996143171f..494f828b91a 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ Issues pertaining to this repository are [labeled ## Contributing If you wish to contribute a new package to the Flutter ecosystem, please -see the documentation for [developing packages](https://flutter.io/developing-packages/). You can store +see the documentation for [developing packages](https://flutter.dev/to/develop-packages). You can store your package source code in any GitHub repository (the present repo is only intended for packages developed by the core Flutter team). Once your package -is ready you can [publish](https://flutter.io/developing-packages/#publish) +is ready you can [publish](https://flutter.dev/to/develop-packages#publish) to the [pub repository](https://pub.dev/). If you wish to contribute a change to any of the existing packages in this repo, @@ -65,7 +65,6 @@ These are the packages hosted in this repository: | [palette\_generator](./packages/palette_generator/) | [![pub package](https://img.shields.io/pub/v/palette_generator.svg)](https://pub.dev/packages/palette_generator) | [![pub points](https://img.shields.io/pub/points/palette_generator)](https://pub.dev/packages/palette_generator/score) | [![popularity](https://img.shields.io/pub/popularity/palette_generator)](https://pub.dev/packages/palette_generator/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20palette_generator?label=)](https://github.com/flutter/flutter/labels/p%3A%20palette_generator) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20palette_generator?label=)](https://github.com/flutter/packages/labels/p%3A%20palette_generator) | | [path\_provider](./packages/path_provider/) | [![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dev/packages/path_provider) | [![pub points](https://img.shields.io/pub/points/path_provider)](https://pub.dev/packages/path_provider/score) | [![popularity](https://img.shields.io/pub/popularity/path_provider)](https://pub.dev/packages/path_provider/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20path_provider?label=)](https://github.com/flutter/flutter/labels/p%3A%20path_provider) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20path_provider?label=)](https://github.com/flutter/packages/labels/p%3A%20path_provider) | | [pigeon](./packages/pigeon/) | [![pub package](https://img.shields.io/pub/v/pigeon.svg)](https://pub.dev/packages/pigeon) | [![pub points](https://img.shields.io/pub/points/pigeon)](https://pub.dev/packages/pigeon/score) | [![popularity](https://img.shields.io/pub/popularity/pigeon)](https://pub.dev/packages/pigeon/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20pigeon?label=)](https://github.com/flutter/flutter/labels/p%3A%20pigeon) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20pigeon?label=)](https://github.com/flutter/packages/labels/p%3A%20pigeon) | -| [platform](./packages/platform/) | [![pub package](https://img.shields.io/pub/v/platform.svg)](https://pub.dev/packages/platform) | [![pub points](https://img.shields.io/pub/points/platform)](https://pub.dev/packages/platform/score) | [![popularity](https://img.shields.io/pub/popularity/platform)](https://pub.dev/packages/platform/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20platform?label=)](https://github.com/flutter/flutter/labels/p%3A%20platform) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20platform?label=)](https://github.com/flutter/packages/labels/p%3A%20platform) | | [pointer\_interceptor](./packages/pointer_interceptor/) | [![pub package](https://img.shields.io/pub/v/pointer_interceptor.svg)](https://pub.dev/packages/pointer_interceptor) | [![pub points](https://img.shields.io/pub/points/pointer_interceptor)](https://pub.dev/packages/pointer_interceptor/score) | [![popularity](https://img.shields.io/pub/popularity/pointer_interceptor)](https://pub.dev/packages/pointer_interceptor/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20pointer_interceptor?label=)](https://github.com/flutter/flutter/labels/p%3A%20pointer_interceptor) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20pointer_interceptor?label=)](https://github.com/flutter/packages/labels/p%3A%20pointer_interceptor) | | [plugin\_platform\_interface](./packages/plugin_platform_interface/) | [![pub package](https://img.shields.io/pub/v/plugin_platform_interface.svg)](https://pub.dev/packages/plugin_platform_interface) | [![pub points](https://img.shields.io/pub/points/plugin_platform_interface)](https://pub.dev/packages/plugin_platform_interface/score) | [![popularity](https://img.shields.io/pub/popularity/plugin_platform_interface)](https://pub.dev/packages/plugin_platform_interface/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20plugin_platform_interface?label=)](https://github.com/flutter/flutter/labels/p%3A%20plugin_platform_interface) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20plugin_platform_interface?label=)](https://github.com/flutter/packages/labels/p%3A%20plugin_platform_interface) | | [process](./packages/process/) | [![pub package](https://img.shields.io/pub/v/process.svg)](https://pub.dev/packages/process) | [![pub points](https://img.shields.io/pub/points/process)](https://pub.dev/packages/process/score) | [![popularity](https://img.shields.io/pub/popularity/process)](https://pub.dev/packages/process/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p%3A%20process?label=)](https://github.com/flutter/flutter/labels/p%3A%20process) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/p%3A%20process?label=)](https://github.com/flutter/packages/labels/p%3A%20process) | diff --git a/analysis_options.yaml b/analysis_options.yaml index 364fc12db89..3f27cc6e111 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -147,7 +147,7 @@ linter: # - prefer_constructors_over_static_methods # far too many false positives - prefer_contains # - prefer_double_quotes # opposite of prefer_single_quotes - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#consider-using--for-short-functions-and-methods - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals @@ -160,7 +160,7 @@ linter: - prefer_if_null_operators - prefer_initializing_formals - prefer_inlined_adds - # - prefer_int_literals # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#use-double-literals-for-double-constants + # - prefer_int_literals # conflicts with https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#use-double-literals-for-double-constants - prefer_interpolation_to_compose_strings - prefer_is_empty - prefer_is_not_empty diff --git a/packages/animations/CHANGELOG.md b/packages/animations/CHANGELOG.md index 45c5e527f73..2d6a0425fdd 100644 --- a/packages/animations/CHANGELOG.md +++ b/packages/animations/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.0.11 diff --git a/packages/animations/example/android/build.gradle b/packages/animations/example/android/build.gradle index 582d60a2faa..d13ef556e26 100644 --- a/packages/animations/example/android/build.gradle +++ b/packages/animations/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/animations/example/android/settings.gradle b/packages/animations/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/animations/example/android/settings.gradle +++ b/packages/animations/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/animations/example/pubspec.yaml b/packages/animations/example/pubspec.yaml index 2050ce2aa0a..2b9660aa4eb 100644 --- a/packages/animations/example/pubspec.yaml +++ b/packages/animations/example/pubspec.yaml @@ -6,8 +6,8 @@ publish_to: none version: 0.0.1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: animations: diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 8ad0b49fe98..53a6c4ca1ad 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,5 +1,18 @@ -## NEXT +## 0.11.0+1 +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. +* Adds note to `README.md` about allowing image streaming in the background on Android. + +## 0.11.0 + +* **Breaking Change** Changes the Android implementation of the camera plugin from `camera_android` + to `camera_android_camerax`, which has better support for a wider range of devices. The CameraX + implementation full feature parity with `camera_android` except for the limitations listed in + `README.md`. To continue using `camera_android`, follow [these instructions](https://pub.dev/packages/camera_android#usage). + +## 0.10.6 + +* Adds support to control video fps and bitrate. See `CameraController` constructor. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates support matrix in README to indicate that iOS 11 is no longer supported. * Clients on versions of Flutter that still support iOS 11 can continue to use this diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index 5458b926c49..1ebac1e02f0 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -17,9 +17,7 @@ A Flutter plugin for iOS, Android and Web allowing access to the device cameras. * Record video. * Add access to the image stream from Dart. -## Installation - -First, add `camera` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). +## Setup ### iOS @@ -45,7 +43,13 @@ Change the minimum Android sdk version to 21 (or higher) in your `android/app/bu minSdkVersion 21 ``` -It's important to note that the `MediaRecorder` class is not working properly on emulators, as stated in the documentation: https://developer.android.com/reference/android/media/MediaRecorder. Specifically, when recording a video with sound enabled and trying to play it back, the duration won't be correct and you will only see the first frame. +The endorsed [`camera_android_camerax`][2] implementation of the camera plugin built with CameraX has +better support for more devices than `camera_android`, but has some limitations; please see [this list][3] +for more details. If you wish to use the [`camera_android`][4] implementation of the camera plugin +built with Camera2 that lacks these limitations, please follow [these instructions][5]. + +If you wish to allow image streaming while your app is in the background, there are additional steps required; +please see [these instructions][6] for more details. ### Web integration @@ -167,3 +171,8 @@ class _CameraAppState extends State { For a more elaborate usage example see [here](https://github.com/flutter/packages/tree/main/packages/camera/camera/example). [1]: https://pub.dev/packages/camera_web#limitations-on-the-web-platform +[2]: https://pub.dev/packages/camera_android_camerax +[3]: https://pub.dev/packages/camera_android_camerax#limitations +[4]: https://pub.dev/packages/camera_android +[5]: https://pub.dev/packages/camera_android#usage +[6]: https://pub.dev/packages/camera_android_camerax#allowing-image-streaming-in-the-background diff --git a/packages/camera/camera/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/camera/camera/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/camera/camera/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/camera/camera/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/camera/camera/example/android/build.gradle b/packages/camera/camera/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/camera/camera/example/android/build.gradle +++ b/packages/camera/camera/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/camera/camera/example/android/settings.gradle b/packages/camera/camera/example/android/settings.gradle index ba3bd2e672c..e54a7e1fd6e 100644 --- a/packages/camera/camera/example/android/settings.gradle +++ b/packages/camera/camera/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { @@ -25,4 +25,4 @@ buildscript { classpath "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" } } -apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" \ No newline at end of file +apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index 1985d1793c6..e1fe14a6132 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -145,6 +145,40 @@ void main() { skip: true, ); + testWidgets('Video capture records valid video', (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + await controller.initialize(); + await controller.prepareForVideoRecording(); + + await controller.startVideoRecording(); + final int recordingStart = DateTime.now().millisecondsSinceEpoch; + + sleep(const Duration(seconds: 2)); + + final XFile file = await controller.stopVideoRecording(); + final int recordingTime = + DateTime.now().millisecondsSinceEpoch - recordingStart; + + final File videoFile = File(file.path); + final VideoPlayerController videoController = VideoPlayerController.file( + videoFile, + ); + await videoController.initialize(); + final int duration = videoController.value.duration.inMilliseconds; + await videoController.dispose(); + + expect(duration, lessThan(recordingTime)); + }); + testWidgets('Pause and resume video recording', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { @@ -162,26 +196,21 @@ void main() { int startPause; int timePaused = 0; + const int pauseIterations = 2; await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); - await controller.pauseVideoRecording(); - startPause = DateTime.now().millisecondsSinceEpoch; - sleep(const Duration(milliseconds: 500)); - await controller.resumeVideoRecording(); - timePaused += DateTime.now().millisecondsSinceEpoch - startPause; - - sleep(const Duration(milliseconds: 500)); - - await controller.pauseVideoRecording(); - startPause = DateTime.now().millisecondsSinceEpoch; - sleep(const Duration(milliseconds: 500)); - await controller.resumeVideoRecording(); - timePaused += DateTime.now().millisecondsSinceEpoch - startPause; + for (int i = 0; i < pauseIterations; i++) { + await controller.pauseVideoRecording(); + startPause = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + await controller.resumeVideoRecording(); + timePaused += DateTime.now().millisecondsSinceEpoch - startPause; - sleep(const Duration(milliseconds: 500)); + sleep(const Duration(milliseconds: 500)); + } final XFile file = await controller.stopVideoRecording(); final int recordingTime = diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index 6555d3f352c..f3b240b56c2 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the camera plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: camera: diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 1d402afcc2e..5f90a0a740a 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -232,12 +232,30 @@ class CameraValue { /// To show the camera preview on the screen use a [CameraPreview] widget. class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. + /// + /// - [resolutionPreset] affect the quality of video recording and image capture. + /// - [enableAudio] controls audio presence in recorded video. + /// + /// Following parameters (if present) will overwrite [resolutionPreset] settings: + /// - [fps] controls rate at which frames should be captured by the camera in frames per second. + /// - [videoBitrate] controls the video encoding bit rate for recording. + /// - [audioBitrate] controls the audio encoding bit rate for recording. + CameraController( CameraDescription description, - this.resolutionPreset, { - this.enableAudio = true, + ResolutionPreset resolutionPreset, { + bool enableAudio = true, + int? fps, + int? videoBitrate, + int? audioBitrate, this.imageFormatGroup, - }) : super(CameraValue.uninitialized(description)); + }) : mediaSettings = MediaSettings( + resolutionPreset: resolutionPreset, + enableAudio: enableAudio, + fps: fps, + videoBitrate: videoBitrate, + audioBitrate: audioBitrate), + super(CameraValue.uninitialized(description)); /// The properties of the camera device controlled by this controller. CameraDescription get description => value.description; @@ -248,10 +266,19 @@ class CameraController extends ValueNotifier { /// if unavailable a lower resolution will be used. /// /// See also: [ResolutionPreset]. - final ResolutionPreset resolutionPreset; + ResolutionPreset get resolutionPreset => + mediaSettings.resolutionPreset ?? ResolutionPreset.max; /// Whether to include audio when recording a video. - final bool enableAudio; + bool get enableAudio => mediaSettings.enableAudio; + + /// The media settings this controller is targeting. + /// + /// This media settings are not guaranteed to be available on the device, + /// if unavailable a [resolutionPreset] default values will be used. + /// + /// See also: [MediaSettings]. + final MediaSettings mediaSettings; /// The [ImageFormatGroup] describes the output of the raw image format. /// @@ -265,6 +292,7 @@ class CameraController extends ValueNotifier { bool _isDisposed = false; StreamSubscription? _imageStreamSubscription; + // A Future awaiting an attempt to initialize (e.g. after `initialize` was // just called). If the controller has not been initialized at least once, // this value is null. @@ -313,10 +341,9 @@ class CameraController extends ValueNotifier { ); }); - _cameraId = await CameraPlatform.instance.createCamera( + _cameraId = await CameraPlatform.instance.createCameraWithSettings( description, - resolutionPreset, - enableAudio: enableAudio, + mediaSettings, ); _unawaited(CameraPlatform.instance @@ -372,7 +399,7 @@ class CameraController extends ValueNotifier { /// Pauses the current camera preview Future pausePreview() async { - if (value.isPreviewPaused) { + if (value.isPreviewPaused || !value.isInitialized || _isDisposed) { return; } try { @@ -923,7 +950,7 @@ class Optional extends IterableBase { if (_value == null) { throw StateError('value called on absent Optional.'); } - return _value!; + return _value; } /// Executes a function if the Optional value is present. @@ -960,7 +987,7 @@ class Optional extends IterableBase { Optional transform(S Function(T value) transformer) { return _value == null ? Optional.absent() - : Optional.of(transformer(_value as T)); + : Optional.of(transformer(_value)); } /// Transforms the Optional value. @@ -971,7 +998,7 @@ class Optional extends IterableBase { Optional transformNullable(S? Function(T value) transformer) { return _value == null ? Optional.absent() - : Optional.fromNullable(transformer(_value as T)); + : Optional.fromNullable(transformer(_value)); } @override diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index dc5755bb882..f00a7e798f0 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,27 +4,27 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.5+9 +version: 0.11.0+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.3 + flutter: ">=3.16.6" flutter: plugin: platforms: android: - default_package: camera_android + default_package: camera_android_camerax ios: default_package: camera_avfoundation web: default_package: camera_web dependencies: - camera_android: ^0.10.7 - camera_avfoundation: ^0.9.13 - camera_platform_interface: ^2.5.0 - camera_web: ^0.3.1 + camera_android_camerax: ^0.6.5 + camera_avfoundation: ^0.9.15 + camera_platform_interface: ^2.6.0 + camera_web: ^0.3.3 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index c73e1816445..4b5a2a5b516 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -68,6 +69,15 @@ class FakeController extends ValueNotifier @override ResolutionPreset get resolutionPreset => ResolutionPreset.low; + @override + MediaSettings get mediaSettings => const MediaSettings( + resolutionPreset: ResolutionPreset.low, + fps: 15, + videoBitrate: 200000, + audioBitrate: 32000, + enableAudio: true, + ); + @override Future resumeVideoRecording() async {} diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index ec111ed8594..f10015334d5 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -111,6 +111,46 @@ void main() { expect(cameraController.value.isInitialized, isTrue); }); + test('can be initialized with media settings', () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.low, + fps: 15, + videoBitrate: 200000, + audioBitrate: 32000, + enableAudio: false, + ); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, const Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + expect(cameraController.resolutionPreset, ResolutionPreset.low); + expect(cameraController.enableAudio, false); + expect(cameraController.mediaSettings.fps, 15); + expect(cameraController.mediaSettings.videoBitrate, 200000); + expect(cameraController.mediaSettings.audioBitrate, 32000); + }); + + test('default constructor initializes media settings', () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.resolutionPreset, ResolutionPreset.max); + expect(cameraController.enableAudio, true); + expect(cameraController.mediaSettings.fps, isNull); + expect(cameraController.mediaSettings.videoBitrate, isNull); + expect(cameraController.mediaSettings.audioBitrate, isNull); + }); + test('can be disposed', () async { final CameraController cameraController = CameraController( const CameraDescription( @@ -1429,15 +1469,20 @@ class MockCameraPlatform extends Mock Future> availableCameras() => Future>.value(mockAvailableCameras); + @override + Future createCameraWithSettings( + CameraDescription cameraDescription, MediaSettings? mediaSettings) => + mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockInitializeCamera); + @override Future createCamera( CameraDescription description, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) => - mockPlatformException - ? throw PlatformException(code: 'foo', message: 'bar') - : Future.value(mockInitializeCamera); + createCameraWithSettings(description, null); @override Stream onCameraInitialized(int cameraId) => diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 66cd2dc329e..777f4913eeb 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,29 @@ +## 0.10.9+7 + +* Updates Android Gradle plugin to 8.5.0. + +## 0.10.9+6 + +* Reverts changes to support Impeller. + +## 0.10.9+5 + +* Updates annotations lib to 1.8.0. + +## 0.10.9+4 + +* [Supports Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). + +## 0.10.9+3 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 0.10.9+2 + +* Updates `README.md` to reflect the fact that the `camera_android_camerax` camera plugin implementation + is the endorsed Android implementation for `camera: ^0.11.0`. + ## 0.10.9+1 * Changes the visibility of a number of fields to `@VisibleForTesting` in order simplify testing. @@ -26,7 +52,7 @@ ## 0.10.8+14 -* Fixes `pausePreview` null pointer error. `pausePreview` should not be called +* Fixes `pausePreview` null pointer error. `pausePreview` should not be called when camera is closed or not configured. * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. diff --git a/packages/camera/camera_android/README.md b/packages/camera/camera_android/README.md index 31f2d66ae4e..36f1dd5c3c0 100644 --- a/packages/camera/camera_android/README.md +++ b/packages/camera/camera_android/README.md @@ -1,24 +1,23 @@ # camera\_android -The Android implementation of [`camera`][1]. - -*Note*: [`camera_android_camerax`][3] will become the default implementation of -`camera` on Android by May 2024, so **we strongly encourage you to opt into it** -by using [these instructions][4]. If any [limitations][5] of `camera_android_camerax` -prevent you from using it or if you run into any problems, please report these -issues under [`flutter/flutter`][5] with `[camerax]` in the title. +An Android implementation of [`camera`][1] built with the [Camera2 library][4]. ## Usage -This package is [endorsed][2], which means you can simply use `camera` -normally. This package will be automatically included in your app when you do, -so you do not need to add it to your `pubspec.yaml`. +As of `camera: ^0.11.0`, to use this plugin instead of [`camera_android_camerax`][3], +run + +```sh +$ flutter pub add camera_android +``` -However, if you `import` this package to use any of its APIs directly, you -should add it to your `pubspec.yaml` as usual. +## Limitation of testing video recording on emulators +`MediaRecorder` does not work properly on emulators, as stated in [the documentation][5]. Specifically, +when recording a video with sound enabled and trying to play it back, the duration won't be correct and +you will only see the first frame. [1]: https://pub.dev/packages/camera -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://pub.dev/packages/camera_android_camerax -[4]: https://pub.dev/packages/camera_android_camerax#usage -[5]: https://pub.dev/packages/camera_android_camerax#limitations +[4]: https://developer.android.com/media/camera/camera2 +[5]: https://developer.android.com/reference/android/media/MediaRecorder diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index dfa21b72bb8..005d5d00985 100644 --- a/packages/camera/camera_android/android/build.gradle +++ b/packages/camera/camera_android/android/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:8.5.0' } } @@ -65,7 +65,7 @@ buildFeatures { } dependencies { - implementation 'androidx.annotation:annotation:1.7.1' + implementation 'androidx.annotation:annotation:1.8.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/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index 308d4283aa2..aff225e2c35 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -19,9 +19,6 @@ * *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. * See {@code io.flutter.plugins.camera.MainActivity} for an example. - * - *

Call {@link #registerWith(io.flutter.plugin.common.PluginRegistry.Registrar)} to register an - * implementation of this that uses the stable {@code io.flutter.plugin.common} package. */ public final class CameraPlugin implements FlutterPlugin, ActivityAware { @@ -36,24 +33,6 @@ public final class CameraPlugin implements FlutterPlugin, ActivityAware { */ public CameraPlugin() {} - /** - * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} - * package. - * - *

Calling this automatically initializes the plugin. However plugins initialized this way - * won't react to changes in activity or context, unlike {@link CameraPlugin}. - */ - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - CameraPlugin plugin = new CameraPlugin(); - plugin.maybeStartListening( - registrar.activity(), - registrar.messenger(), - registrar::addRequestPermissionsResultListener, - registrar.view()); - } - @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { this.flutterPluginBinding = binding; diff --git a/packages/camera/camera_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/camera/camera_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/camera/camera_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/camera/camera_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/camera/camera_android/example/android/build.gradle b/packages/camera/camera_android/example/android/build.gradle index 07336127c08..d38534d066a 100644 --- a/packages/camera/camera_android/example/android/build.gradle +++ b/packages/camera/camera_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/camera/camera_android/example/android/settings.gradle b/packages/camera/camera_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/camera/camera_android/example/android/settings.gradle +++ b/packages/camera/camera_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/camera/camera_android/example/lib/camera_controller.dart b/packages/camera/camera_android/example/lib/camera_controller.dart index 2ebd975531e..17308a4e0de 100644 --- a/packages/camera/camera_android/example/lib/camera_controller.dart +++ b/packages/camera/camera_android/example/lib/camera_controller.dart @@ -494,7 +494,7 @@ class Optional extends IterableBase { if (_value == null) { throw StateError('value called on absent Optional.'); } - return _value!; + return _value; } /// Executes a function if the Optional value is present. diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index 24e3d63767e..cd212825d74 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the camera plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: camera_android: diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 06958bbd322..0dc0371c0e4 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,11 +3,11 @@ 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.9+1 +version: 0.10.9+7 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index f719078b681..8307d7077d8 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,49 @@ +## 0.6.5+6 + +* Updates Guava version to 33.2.1. +* Updates CameraX version to 1.3.4. + +## 0.6.5+5 + +* Reverts changes to support Impeller. + +## 0.6.5+4 + +* [Supports Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). + +## 0.6.5+3 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Adds notes to `README.md` about allowing image streaming in the background and the required + `WRITE_EXTERNAL_STORAGE` permission specified in the plugin to allow writing photos and videos to + files. + +## 0.6.5+2 + +* Update to latest stable camerax `1.3.3`. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 0.6.5+1 + +* Updates `README.md` to reflect the fact that the `camera_android_camerax` camera plugin implementation + is the endorsed Android implementation for `camera: ^0.11.0`. + +## 0.6.5 + +* Modifies `stopVideoRecording` to ensure that the method only returns when CameraX reports that the + recorded video finishes saving to a file. +* Modifies `startVideoCapturing` to ensure that the method only returns when CameraX reports that + video recording has started. +* Adds empty implementation for `setDescriptionWhileRecording` and leaves a todo to add this feature. + +## 0.6.4+1 + +* Adds empty implementation for `prepareForVideoRecording` since this optimization is not used on Android. + +## 0.6.4 + +* Prevents usage of unsupported concurrent `UseCase`s based on the capabiliites of the camera device. + ## 0.6.3 * Shortens default interval that internal Java `InstanceManager` uses to release garbage collected weak references to diff --git a/packages/camera/camera_android_camerax/CONTRIBUTING.md b/packages/camera/camera_android_camerax/CONTRIBUTING.md index d6e7fe61bdd..43377224628 100644 --- a/packages/camera/camera_android_camerax/CONTRIBUTING.md +++ b/packages/camera/camera_android_camerax/CONTRIBUTING.md @@ -74,5 +74,5 @@ testing purposes. To generate the mock objects, run [2]: https://docs.google.com/document/d/1wXB1zNzYhd2SxCu1_BK3qmNWRhonTB6qdv4erdtBQqo/edit?usp=sharing&resourcekey=0-WOBqqOKiO9SARnziBg28pg [3]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md [4]: https://pub.dev/packages/mockito -[5]: https://github.com/flutter/flutter/wiki/Plugin-Tests#running-tests -[6]: https://github.com/flutter/flutter/wiki/Chat \ No newline at end of file +[5]: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#running-tests +[6]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 64a56f1a3b5..b6b1a46a383 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -1,31 +1,48 @@ # camera\_android\_camerax -An Android implementation of [`camera`][1] that uses the [CameraX library][2]. +The Android implementation of [`camera`][1] built with the [CameraX library][2]. -*Note*: This implementation will become the default implementation of `camera` -on Android by May 2024, so **we strongly encourage you to opt into it** -by using [the instructions](#usage) below. If any of [the limitations](#limitations) -prevent you from using `camera_android_camerax` or if you run into any problems, -please report these issues under [`flutter/flutter`][5] with `[camerax]` in -the title. +*Note*: If any of [the limitations](#limitations) prevent you from using +using `camera_android_camerax` or if you run into any problems, please report +report these issues under [`flutter/flutter`][5] with `[camerax]` in the title. +You may also opt back into the [`camera_android`][9] implementation if you need. ## Usage -To use this plugin instead of [`camera_android`][4], run +As of `camera: ^0.11.0`, this package is [endorsed][3], which means you can +simply use `camera` normally. This package will be automatically be included +in your app when you do, so you do not need to add it to your `pubspec.yaml`. -```sh -$ flutter pub add camera_android_camerax -``` - -from your project's root directory. +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. ## Limitations +### Concurrent preview display, video recording, image capture, and image streaming + +The CameraX plugin only supports the concurrent camera use cases supported by Camerax; see +[their documentation][6] for more information. To avoid the usage of unsupported concurrent +use cases, the plugin behaves according to the following: + +* If the preview is paused (via `pausePreview`), concurrent video recording and image capture + and/or image streaming (via `startVideoCapturing(cameraId, VideoCaptureOptions(streamCallback:...))`) + is supported. +* If the preview is not paused + * **and** the camera device is at least supported hardware [`LIMITED`][8], then concurrent + image capture and video recording is supported. + * **and** the camera device is at least supported hardware [`LEVEL_3`][7], then concurrent + video recording and image streaming is supported, but concurrent video recording, image + streaming, and image capture is not supported. + +### `setDescriptionWhileRecording` is unimplemented [Issue #148013][148013] +`setDescriptionWhileRecording`, used to switch cameras while recording video, is currently unimplemented +due to this not currently being supported by CameraX. + ### 240p resolution configuration for video recording -240p resolution configuration for video recording is unsupported by CameraX, -and thus, the plugin will fall back to 480p if configured with a -`ResolutionPreset`. +240p resolution configuration for video recording is unsupported by CameraX, and thus, +the plugin will fall back to target 480p (`ResolutionPreset.medium`) if configured with +`ResolutionPreset.low`. ### Setting maximum duration and stream options for video capture @@ -34,6 +51,28 @@ Calling `startVideoCapturing` with `VideoCaptureOptions` configured with limitations of the CameraX library and the platform interface, respectively, and thus, those parameters will silently be ignored. +## What requires Android permissions + +### Writing to external storage to save image files + +In order to save captured images and videos to files on Android 10 and below, CameraX +requires specifying the `WRITE_EXTERNAL_STORAGE` permission (see [the CameraX documentation][10]). +This is already done in the plugin, so no further action is required on your end. To understand +the implications of specificying this permission, see [the `WRITE_EXTERNAL_STORAGE` documentation][11]. + +### Allowing image streaming in the background + +As of Android 14, to allow for background image streaming, you will need to specify the foreground +[`TYPE_CAMERA`][12] foreground service permission in your app's manifest. Specifically, in +`your_app/android/app/src/main/AndroidManifest.xml` add the following: + +```xml + + + ... + +``` + ## Contributing For more information on contributing to this plugin, see [`CONTRIBUTING.md`](CONTRIBUTING.md). @@ -42,14 +81,14 @@ For more information on contributing to this plugin, see [`CONTRIBUTING.md`](CON [1]: https://pub.dev/packages/camera [2]: https://developer.android.com/training/camerax -[3]: https://docs.flutter.dev/packages-and-plugins/developing-packages#non-endorsed-federated-plugin +[3]: https://flutter.dev/to/endorsed-federated-plugin [4]: https://pub.dev/packages/camera_android [5]: https://github.com/flutter/flutter/issues/new/choose -[120462]: https://github.com/flutter/flutter/issues/120462 -[125915]: https://github.com/flutter/flutter/issues/125915 -[120715]: https://github.com/flutter/flutter/issues/120715 -[120468]: https://github.com/flutter/flutter/issues/120468 -[120467]: https://github.com/flutter/flutter/issues/120467 -[125371]: https://github.com/flutter/flutter/issues/125371 -[126477]: https://github.com/flutter/flutter/issues/126477 -[127896]: https://github.com/flutter/flutter/issues/127896 +[6]: https://developer.android.com/media/camera/camerax/architecture#combine-use-cases +[7]: https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_3 +[8]: https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED +[9]: https://pub.dev/packages/camera_android#usage +[10]: https://developer.android.com/media/camera/camerax/architecture#permissions +[11]: https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE +[12]: https://developer.android.com/reference/android/Manifest.permission#FOREGROUND_SERVICE_CAMERA +[148013]: https://github.com/flutter/flutter/issues/148013 diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle index dc61dd2640c..71d87a7522e 100644 --- a/packages/camera/camera_android_camerax/android/build.gradle +++ b/packages/camera/camera_android_camerax/android/build.gradle @@ -61,12 +61,12 @@ android { dependencies { // CameraX core library using the camera2 implementation must use same version number. - def camerax_version = "1.3.0-beta01" + def camerax_version = "1.3.4" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" implementation "androidx.camera:camera-video:${camerax_version}" - implementation 'com.google.guava:guava:32.0.1-android' + implementation 'com.google.guava:guava:33.2.1-android' 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_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoFlutterApiImpl.java new file mode 100644 index 00000000000..398fc38049a --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoFlutterApiImpl.java @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.camera.camera2.interop.Camera2CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraInfoFlutterApi; + +public class Camera2CameraInfoFlutterApiImpl extends Camera2CameraInfoFlutterApi { + private final InstanceManager instanceManager; + + public Camera2CameraInfoFlutterApiImpl( + @Nullable BinaryMessenger binaryMessenger, @Nullable InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + void create(@NonNull Camera2CameraInfo camera2CameraInfo, @Nullable Reply reply) { + if (!instanceManager.containsInstance(camera2CameraInfo)) { + create(instanceManager.addHostCreatedInstance(camera2CameraInfo), reply); + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java new file mode 100644 index 00000000000..43fd0a383b0 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java @@ -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. + +package io.flutter.plugins.camerax; + +import android.content.Context; +import android.hardware.camera2.CameraCharacteristics; +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.annotation.VisibleForTesting; +import androidx.camera.camera2.interop.Camera2CameraInfo; +import androidx.camera.camera2.interop.ExperimentalCamera2Interop; +import androidx.camera.core.CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraInfoHostApi; +import java.util.Objects; + +/** + * Host API implementation for {@link Camera2CameraInfo}. + * + *

This class handles instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class Camera2CameraInfoHostApiImpl implements Camera2CameraInfoHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private final Camera2CameraInfoProxy proxy; + + /** Proxy for methods of {@link Camera2CameraInfo}. */ + @VisibleForTesting + public static class Camera2CameraInfoProxy { + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public Camera2CameraInfo createFrom(@NonNull CameraInfo cameraInfo) { + return Camera2CameraInfo.from(cameraInfo); + } + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public Integer getSupportedHardwareLevel(@NonNull Camera2CameraInfo camera2CameraInfo) { + return camera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + } + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public String getCameraId(@NonNull Camera2CameraInfo camera2CameraInfo) { + return camera2CameraInfo.getCameraId(); + } + } + + /** + * Constructs an {@link Camera2CameraInfoHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param context {@link Context} used to retrieve {@code Executor} + */ + public Camera2CameraInfoHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this(binaryMessenger, instanceManager, new Camera2CameraInfoProxy()); + } + + /** + * Constructs an {@link Camera2CameraInfoHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for methods of {@link Camera2CameraInfo} + */ + @VisibleForTesting + Camera2CameraInfoHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull Camera2CameraInfoProxy proxy) { + this.instanceManager = instanceManager; + this.binaryMessenger = binaryMessenger; + this.proxy = proxy; + } + + @Override + @NonNull + public Long createFrom(@NonNull Long cameraInfoIdentifier) { + final CameraInfo cameraInfo = + Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)); + final Camera2CameraInfo camera2CameraInfo = proxy.createFrom(cameraInfo); + final Camera2CameraInfoFlutterApiImpl flutterApi = + new Camera2CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); + + flutterApi.create(camera2CameraInfo, reply -> {}); + return instanceManager.getIdentifierForStrongReference(camera2CameraInfo); + } + + @Override + @NonNull + public Long getSupportedHardwareLevel(@NonNull Long identifier) { + return Long.valueOf(proxy.getSupportedHardwareLevel(getCamera2CameraInfoInstance(identifier))); + } + + @Override + @NonNull + public String getCameraId(@NonNull Long identifier) { + return proxy.getCameraId(getCamera2CameraInfoInstance(identifier)); + } + + private Camera2CameraInfo getCamera2CameraInfoInstance(@NonNull Long identifier) { + return Objects.requireNonNull(instanceManager.getInstance(identifier)); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index d281bbe65a8..42e5df0f32c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -136,6 +136,8 @@ public void setUp( GeneratedCameraXLibrary.MeteringPointHostApi.setup(binaryMessenger, meteringPointHostApiImpl); GeneratedCameraXLibrary.ResolutionFilterHostApi.setup( binaryMessenger, new ResolutionFilterHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.Camera2CameraInfoHostApi.setup( + binaryMessenger, new Camera2CameraInfoHostApiImpl(binaryMessenger, instanceManager)); } @Override 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 fa6be792096..cbfc36f35cb 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 @@ -146,6 +146,22 @@ private VideoResolutionFallbackRule(final int index) { } } + /** + * Video recording status. + * + *

See https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent. + */ + public enum VideoRecordEvent { + START(0), + FINALIZE(1); + + final int index; + + private VideoRecordEvent(final int index) { + this.index = index; + } + } + /** * The types of capture request options this plugin currently supports. * @@ -558,6 +574,55 @@ ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class VideoRecordEventData { + private @NonNull VideoRecordEvent value; + + public @NonNull VideoRecordEvent getValue() { + return value; + } + + public void setValue(@NonNull VideoRecordEvent setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"value\" is null."); + } + this.value = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + VideoRecordEventData() {} + + public static final class Builder { + + private @Nullable VideoRecordEvent value; + + public @NonNull Builder setValue(@NonNull VideoRecordEvent setterArg) { + this.value = setterArg; + return this; + } + + public @NonNull VideoRecordEventData build() { + VideoRecordEventData pigeonReturn = new VideoRecordEventData(); + pigeonReturn.setValue(value); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(value == null ? null : value.index); + return toListResult; + } + + static @NonNull VideoRecordEventData fromList(@NonNull ArrayList list) { + VideoRecordEventData pigeonResult = new VideoRecordEventData(); + Object value = list.get(0); + pigeonResult.setValue(value == null ? null : VideoRecordEvent.values()[(int) value]); + return pigeonResult; + } + } + /** * Convenience class for building [FocusMeteringAction]s with multiple metering points. * @@ -2118,6 +2183,34 @@ static void setup( } } } + + private static class PendingRecordingFlutterApiCodec extends StandardMessageCodec { + public static final PendingRecordingFlutterApiCodec INSTANCE = + new PendingRecordingFlutterApiCodec(); + + private PendingRecordingFlutterApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return VideoRecordEventData.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof VideoRecordEventData) { + stream.write(128); + writeValue(stream, ((VideoRecordEventData) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class PendingRecordingFlutterApi { private final @NonNull BinaryMessenger binaryMessenger; @@ -2133,7 +2226,7 @@ public interface Reply { } /** The codec used by PendingRecordingFlutterApi. */ static @NonNull MessageCodec getCodec() { - return new StandardMessageCodec(); + return PendingRecordingFlutterApiCodec.INSTANCE; } public void create(@NonNull Long identifierArg, @NonNull Reply callback) { @@ -2144,6 +2237,18 @@ public void create(@NonNull Long identifierArg, @NonNull Reply callback) { new ArrayList(Collections.singletonList(identifierArg)), channelReply -> callback.reply(null)); } + + public void onVideoRecordingEvent( + @NonNull VideoRecordEventData eventArg, @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.PendingRecordingFlutterApi.onVideoRecordingEvent", + getCodec()); + channel.send( + new ArrayList(Collections.singletonList(eventArg)), + channelReply -> callback.reply(null)); + } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface RecordingHostApi { @@ -4027,6 +4132,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { return ResolutionInfo.fromList((ArrayList) readValue(buffer)); case (byte) 134: return VideoQualityData.fromList((ArrayList) readValue(buffer)); + case (byte) 135: + return VideoRecordEventData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -4055,6 +4162,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof VideoQualityData) { stream.write(134); writeValue(stream, ((VideoQualityData) value).toList()); + } else if (value instanceof VideoRecordEventData) { + stream.write(135); + writeValue(stream, ((VideoRecordEventData) value).toList()); } else { super.writeValue(stream, value); } @@ -4267,4 +4377,137 @@ static void setup( } } } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface Camera2CameraInfoHostApi { + + @NonNull + Long createFrom(@NonNull Long cameraInfoIdentifier); + + @NonNull + Long getSupportedHardwareLevel(@NonNull Long identifier); + + @NonNull + String getCameraId(@NonNull Long identifier); + + /** The codec used by Camera2CameraInfoHostApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + /** + * Sets up an instance of `Camera2CameraInfoHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup( + @NonNull BinaryMessenger binaryMessenger, @Nullable Camera2CameraInfoHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number cameraInfoIdentifierArg = (Number) args.get(0); + try { + Long output = + api.createFrom( + (cameraInfoIdentifierArg == null) + ? null + : cameraInfoIdentifierArg.longValue()); + 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.Camera2CameraInfoHostApi.getSupportedHardwareLevel", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + Long output = + api.getSupportedHardwareLevel( + (identifierArg == null) ? null : identifierArg.longValue()); + 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.Camera2CameraInfoHostApi.getCameraId", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + String output = + api.getCameraId((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class Camera2CameraInfoFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public Camera2CameraInfoFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by Camera2CameraInfoFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void create(@NonNull Long identifierArg, @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Collections.singletonList(identifierArg)), + channelReply -> callback.reply(null)); + } + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingFlutterApiImpl.java index 9b4f7108056..b3c46769ad9 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingFlutterApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingFlutterApiImpl.java @@ -9,6 +9,8 @@ import androidx.camera.video.PendingRecording; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.PendingRecordingFlutterApi; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoRecordEvent; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.VideoRecordEventData; public class PendingRecordingFlutterApiImpl extends PendingRecordingFlutterApi { private final InstanceManager instanceManager; @@ -22,4 +24,14 @@ public PendingRecordingFlutterApiImpl( void create(@NonNull PendingRecording pendingRecording, @Nullable Reply reply) { create(instanceManager.addHostCreatedInstance(pendingRecording), reply); } + + void sendVideoRecordingFinalizedEvent(@NonNull Reply reply) { + super.onVideoRecordingEvent( + new VideoRecordEventData.Builder().setValue(VideoRecordEvent.FINALIZE).build(), reply); + } + + void sendVideoRecordingStartedEvent(@NonNull Reply reply) { + super.onVideoRecordingEvent( + new VideoRecordEventData.Builder().setValue(VideoRecordEvent.START).build(), reply); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java index a1d661d1d9c..93aa39c56be 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java @@ -24,6 +24,8 @@ public class PendingRecordingHostApiImpl implements PendingRecordingHostApi { @VisibleForTesting @NonNull public CameraXProxy cameraXProxy = new CameraXProxy(); + @VisibleForTesting PendingRecordingFlutterApiImpl pendingRecordingFlutterApi; + @VisibleForTesting SystemServicesFlutterApiImpl systemServicesFlutterApi; @VisibleForTesting RecordingFlutterApiImpl recordingFlutterApi; @@ -37,6 +39,8 @@ public PendingRecordingHostApiImpl( this.context = context; systemServicesFlutterApi = cameraXProxy.createSystemServicesFlutterApiImpl(binaryMessenger); recordingFlutterApi = new RecordingFlutterApiImpl(binaryMessenger, instanceManager); + pendingRecordingFlutterApi = + new PendingRecordingFlutterApiImpl(binaryMessenger, instanceManager); } /** Sets the context, which is used to get the {@link Executor} needed to start the recording. */ @@ -73,10 +77,16 @@ public Executor getExecutor() { /** * Handles {@link VideoRecordEvent}s that come in during video recording. Sends any errors * encountered using {@link SystemServicesFlutterApiImpl}. + * + *

Currently only sends {@link VideoRecordEvent.Start} and {@link VideoRecordEvent.Finalize} + * events to the Dart side. */ @VisibleForTesting public void handleVideoRecordEvent(@NonNull VideoRecordEvent event) { - if (event instanceof VideoRecordEvent.Finalize) { + if (event instanceof VideoRecordEvent.Start) { + pendingRecordingFlutterApi.sendVideoRecordingStartedEvent(reply -> {}); + } else if (event instanceof VideoRecordEvent.Finalize) { + pendingRecordingFlutterApi.sendVideoRecordingFinalizedEvent(reply -> {}); VideoRecordEvent.Finalize castedEvent = (VideoRecordEvent.Finalize) event; if (castedEvent.hasError()) { String cameraErrorMessage; diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java new file mode 100644 index 00000000000..a5ab10ff79b --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java @@ -0,0 +1,114 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; +import androidx.camera.camera2.interop.Camera2CameraInfo; +import androidx.camera.core.CameraInfo; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + +public class Camera2CameraInfoTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public Camera2CameraInfo mockCamera2CameraInfo; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.stopFinalizationListener(); + } + + @Test + public void createFrom_createsInstanceFromCameraInfoInstance() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 60; + final CameraInfo mockCameraInfo = mock(CameraInfo.class); + final long cameraInfoIdentifier = 92; + + testInstanceManager.addDartCreatedInstance(mockCameraInfo, cameraInfoIdentifier); + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + + try (MockedStatic mockedCamera2CameraInfo = + Mockito.mockStatic(Camera2CameraInfo.class)) { + mockedCamera2CameraInfo + .when(() -> Camera2CameraInfo.from(mockCameraInfo)) + .thenAnswer((Answer) invocation -> mockCamera2CameraInfo); + + hostApi.createFrom(cameraInfoIdentifier); + assertEquals( + testInstanceManager.getInstance(camera2CameraInfoIdentifier), mockCamera2CameraInfo); + } + } + + @Test + public void getSupportedHardwareLevel_returnsExpectedLevel() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 3; + final int expectedHardwareLevel = CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + when(mockCamera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)) + .thenReturn(expectedHardwareLevel); + + assertEquals( + expectedHardwareLevel, + hostApi.getSupportedHardwareLevel(camera2CameraInfoIdentifier).intValue()); + } + + @Test + public void getCameraId_returnsExpectedId() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 13; + final String expectedCameraId = "testCameraId"; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + when(mockCamera2CameraInfo.getCameraId()).thenReturn(expectedCameraId); + + assertEquals(expectedCameraId, hostApi.getCameraId(camera2CameraInfoIdentifier)); + } + + @Test + public void flutterApiCreate_makesCallToCreateInstanceOnDartSide() { + final Camera2CameraInfoFlutterApiImpl spyFlutterApi = + spy(new Camera2CameraInfoFlutterApiImpl(mock(BinaryMessenger.class), testInstanceManager)); + + spyFlutterApi.create(mockCamera2CameraInfo, reply -> {}); + + final long identifier = + Objects.requireNonNull( + testInstanceManager.getIdentifierForStrongReference(mockCamera2CameraInfo)); + verify(spyFlutterApi).create(eq(identifier), any()); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java index 92415d5381a..f25a17ed5d9 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java @@ -41,6 +41,7 @@ public class PendingRecordingTest { @Mock public RecordingFlutterApiImpl mockRecordingFlutterApi; @Mock public Context mockContext; @Mock public SystemServicesFlutterApiImpl mockSystemServicesFlutterApi; + @Mock public PendingRecordingFlutterApiImpl mockPendingRecordingFlutterApi; @Mock public VideoRecordEvent.Finalize event; @Mock public Throwable throwable; @@ -80,6 +81,7 @@ public void testHandleVideoRecordEventSendsError() { PendingRecordingHostApiImpl pendingRecordingHostApi = new PendingRecordingHostApiImpl(mockBinaryMessenger, testInstanceManager, mockContext); pendingRecordingHostApi.systemServicesFlutterApi = mockSystemServicesFlutterApi; + pendingRecordingHostApi.pendingRecordingFlutterApi = mockPendingRecordingFlutterApi; final String eventMessage = "example failure message"; when(event.hasError()).thenReturn(true); @@ -89,9 +91,35 @@ public void testHandleVideoRecordEventSendsError() { pendingRecordingHostApi.handleVideoRecordEvent(event); + verify(mockPendingRecordingFlutterApi).sendVideoRecordingFinalizedEvent(any()); verify(mockSystemServicesFlutterApi).sendCameraError(eq(eventMessage), any()); } + @Test + public void handleVideoRecordEvent_SendsVideoRecordingFinalizedEvent() { + PendingRecordingHostApiImpl pendingRecordingHostApi = + new PendingRecordingHostApiImpl(mockBinaryMessenger, testInstanceManager, mockContext); + pendingRecordingHostApi.pendingRecordingFlutterApi = mockPendingRecordingFlutterApi; + + when(event.hasError()).thenReturn(false); + + pendingRecordingHostApi.handleVideoRecordEvent(event); + + verify(mockPendingRecordingFlutterApi).sendVideoRecordingFinalizedEvent(any()); + } + + @Test + public void handleVideoRecordEvent_SendsVideoRecordingStartedEvent() { + PendingRecordingHostApiImpl pendingRecordingHostApi = + new PendingRecordingHostApiImpl(mockBinaryMessenger, testInstanceManager, mockContext); + pendingRecordingHostApi.pendingRecordingFlutterApi = mockPendingRecordingFlutterApi; + VideoRecordEvent.Start mockStartEvent = mock(VideoRecordEvent.Start.class); + + pendingRecordingHostApi.handleVideoRecordEvent(mockStartEvent); + + verify(mockPendingRecordingFlutterApi).sendVideoRecordingStartedEvent(any()); + } + @Test public void flutterApiCreateTest() { final PendingRecordingFlutterApiImpl spyPendingRecordingFlutterApi = diff --git a/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/camera/camera_android_camerax/example/android/app/src/main/AndroidManifest.xml b/packages/camera/camera_android_camerax/example/android/app/src/main/AndroidManifest.xml index 82b92e25bdf..2a0066ccab4 100644 --- a/packages/camera/camera_android_camerax/example/android/app/src/main/AndroidManifest.xml +++ b/packages/camera/camera_android_camerax/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController(cameras[0], + mediaSettings: + const MediaSettings(resolutionPreset: ResolutionPreset.low)); + await controller.initialize(); + await controller.prepareForVideoRecording(); + + await controller.startVideoRecording(); + final int recordingStart = DateTime.now().millisecondsSinceEpoch; + + sleep(const Duration(seconds: 2)); + + final XFile file = await controller.stopVideoRecording(); + final int postStopTime = + DateTime.now().millisecondsSinceEpoch - recordingStart; + + final File videoFile = File(file.path); + final VideoPlayerController videoController = VideoPlayerController.file( + videoFile, + ); + await videoController.initialize(); + final int duration = videoController.value.duration.inMilliseconds; + await videoController.dispose(); + + expect(duration, lessThan(postStopTime)); + }); + + testWidgets('Pause and resume video recording', (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController(cameras[0], + mediaSettings: + const MediaSettings(resolutionPreset: ResolutionPreset.low)); + await controller.initialize(); + await controller.prepareForVideoRecording(); + + int startPause; + int timePaused = 0; + const int pauseIterations = 2; + + await controller.startVideoRecording(); + final int recordingStart = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + + for (int i = 0; i < pauseIterations; i++) { + await controller.pauseVideoRecording(); + startPause = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + await controller.resumeVideoRecording(); + timePaused += DateTime.now().millisecondsSinceEpoch - startPause; + + sleep(const Duration(milliseconds: 500)); + } + + final XFile file = await controller.stopVideoRecording(); + final int recordingTime = + DateTime.now().millisecondsSinceEpoch - recordingStart; + + final File videoFile = File(file.path); + final VideoPlayerController videoController = VideoPlayerController.file( + videoFile, + ); + await videoController.initialize(); + final int duration = videoController.value.duration.inMilliseconds; + await videoController.dispose(); + + expect(duration, lessThan(recordingTime - timePaused)); + }); } diff --git a/packages/camera/camera_android_camerax/example/lib/camera_controller.dart b/packages/camera/camera_android_camerax/example/lib/camera_controller.dart index 92aff480e73..bd940aba087 100644 --- a/packages/camera/camera_android_camerax/example/lib/camera_controller.dart +++ b/packages/camera/camera_android_camerax/example/lib/camera_controller.dart @@ -880,7 +880,7 @@ class Optional extends IterableBase { if (_value == null) { throw StateError('value called on absent Optional.'); } - return _value!; + return _value; } /// Executes a function if the Optional value is present. diff --git a/packages/camera/camera_android_camerax/example/pubspec.yaml b/packages/camera/camera_android_camerax/example/pubspec.yaml index 4739dccbae8..f16b8d5433b 100644 --- a/packages/camera/camera_android_camerax/example/pubspec.yaml +++ b/packages/camera/camera_android_camerax/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the camera_android_camerax plugin. publish_to: 'none' environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: camera_android_camerax: 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 e6ef399b657..edf24bab37a 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 @@ -17,8 +17,10 @@ import 'analyzer.dart'; import 'aspect_ratio_strategy.dart'; import 'camera.dart'; import 'camera2_camera_control.dart'; +import 'camera2_camera_info.dart'; import 'camera_control.dart'; import 'camera_info.dart'; +import 'camera_metadata.dart'; import 'camera_selector.dart'; import 'camera_state.dart'; import 'camerax_library.g.dart'; @@ -111,6 +113,12 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting String? videoOutputPath; + /// Stream queue to pick up finalized viceo recording events in + /// [stopVideoRecording]. + final StreamQueue videoRecordingEventStreamQueue = + StreamQueue( + PendingRecording.videoRecordingEventStreamController.stream); + /// Whether or not [preview] has been bound to the lifecycle of the camera by /// [createCamera]. @visibleForTesting @@ -120,7 +128,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// The prefix used to create the filename for video recording files. @visibleForTesting - final String videoPrefix = 'MOV'; + final String videoPrefix = 'REC'; /// The [ImageCapture] instance that can be configured to capture a still image. @visibleForTesting @@ -261,6 +269,8 @@ class AndroidCameraCameraX extends CameraPlatform { cameraName = 'Camera $cameraCount'; cameraCount++; + // TODO(camsim99): Use camera ID retrieved from Camera2CameraInfo as + // camera name: https://github.com/flutter/flutter/issues/147545. cameraDescriptions.add(CameraDescription( name: cameraName, lensDirection: cameraLensDirection, @@ -291,9 +301,9 @@ class AndroidCameraCameraX extends CameraPlatform { /// uninitialized camera instance, this method retrieves a /// [ProcessCameraProvider] instance. /// - /// The specified [resolutionPreset] is the target resolution that CameraX - /// will attempt to select for the [UseCase]s constructed in this method - /// ([preview], [imageCapture], [imageAnalysis], [videoCapture]). If + /// The specified `mediaSettings.resolutionPreset` is the target resolution + /// that CameraX will attempt to select for the [UseCase]s constructed in this + /// method ([preview], [imageCapture], [imageAnalysis], [videoCapture]). If /// unavailable, a fallback behavior of targeting the next highest resolution /// will be attempted. See https://developer.android.com/media/camera/camerax/configuration#specify-resolution. /// @@ -773,13 +783,22 @@ class AndroidCameraCameraX extends CameraPlatform { await _unbindUseCaseFromLifecycle(preview!); } + /// Sets the active camera while recording. + /// + /// Currently unsupported, so is a no-op. + @override + Future setDescriptionWhileRecording(CameraDescription description) { + // TODO(camsim99): Implement this feature, see https://github.com/flutter/flutter/issues/148013. + return Future.value(); + } + /// Resume the paused preview for the selected camera. /// /// [cameraId] not used. @override Future resumePreview(int cameraId) async { _previewIsPaused = false; - await _bindPreviewToLifecycle(cameraId); + await _bindUseCaseToLifecycle(preview!, cameraId); } /// Returns a widget showing a live camera preview. @@ -804,6 +823,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// [cameraId] is not used. @override Future takePicture(int cameraId) async { + await _bindUseCaseToLifecycle(imageCapture!, cameraId); // Set flash mode. if (_currentFlashMode != null) { await imageCapture!.setFlashMode(_currentFlashMode!); @@ -862,6 +882,15 @@ class AndroidCameraCameraX extends CameraPlatform { } } + /// Prepare the capture session for video recording. + /// + /// This optimization is not used on Android, so this implementation is a + /// no-op. + @override + Future prepareForVideoRecording() { + return Future.value(); + } + /// Configures and starts a video recording. Returns silently without doing /// anything if there is currently an active recording. /// @@ -890,12 +919,50 @@ class AndroidCameraCameraX extends CameraPlatform { return; } - if (!(await processCameraProvider!.isBound(videoCapture!))) { - camera = await processCameraProvider! - .bindToLifecycle(cameraSelector!, [videoCapture!]); - await _updateCameraInfoAndLiveCameraState(options.cameraId); + dynamic Function(CameraImageData)? streamCallback = options.streamCallback; + if (!_previewIsPaused) { + // The plugin binds the preview use case to the camera lifecycle when + // createCamera is called, but camera use cases can become limited + // when video recording and displaying a preview concurrently. This logic + // will prioritize attempting to continue displaying the preview, + // stream images, and record video if specified and supported. Otherwise, + // the preview must be paused in order to allow those concurrently. See + // https://developer.android.com/media/camera/camerax/architecture#combine-use-cases + // for more information on supported concurrent camera use cases. + final Camera2CameraInfo camera2CameraInfo = + await proxy.getCamera2CameraInfo(cameraInfo!); + final int cameraInfoSupportedHardwareLevel = + await camera2CameraInfo.getSupportedHardwareLevel(); + + // Handle limited level device restrictions: + final bool cameraSupportsConcurrentImageCapture = + cameraInfoSupportedHardwareLevel != + CameraMetadata.infoSupportedHardwareLevelLegacy; + if (!cameraSupportsConcurrentImageCapture) { + // Concurrent preview + video recording + image capture is not supported + // unless the camera device is cameraSupportsHardwareLevelLimited or + // better. + await _unbindUseCaseFromLifecycle(imageCapture!); + } + + // Handle level 3 device restrictions: + final bool cameraSupportsHardwareLevel3 = + cameraInfoSupportedHardwareLevel == + CameraMetadata.infoSupportedHardwareLevel3; + if (!cameraSupportsHardwareLevel3 || streamCallback == null) { + // Concurrent preview + video recording + image streaming is not supported + // unless the camera device is cameraSupportsHardwareLevel3 or better. + streamCallback = null; + await _unbindUseCaseFromLifecycle(imageAnalysis!); + } else { + // If image streaming concurrently with video recording, image capture + // is unsupported. + await _unbindUseCaseFromLifecycle(imageCapture!); + } } + await _bindUseCaseToLifecycle(videoCapture!, options.cameraId); + // Set target rotation to default CameraX rotation only if capture // orientation not locked. if (!captureOrientationLocked && shouldSetDefaultRotation) { @@ -908,8 +975,14 @@ class AndroidCameraCameraX extends CameraPlatform { pendingRecording = await recorder!.prepareRecording(videoOutputPath!); recording = await pendingRecording!.start(); - if (options.streamCallback != null) { - onStreamedFrameAvailable(options.cameraId).listen(options.streamCallback); + if (streamCallback != null) { + onStreamedFrameAvailable(options.cameraId).listen(streamCallback); + } + + // Wait for video recording to start. + VideoRecordEvent event = await videoRecordingEventStreamQueue.next; + while (event != VideoRecordEvent.start) { + event = await videoRecordingEventStreamQueue.next; } } @@ -927,22 +1000,30 @@ class AndroidCameraCameraX extends CameraPlatform { 'Attempting to stop a ' 'video recording while no recording is in progress.'); } + + /// Stop the active recording and wait for the video recording to be finalized. + await recording!.close(); + VideoRecordEvent event = await videoRecordingEventStreamQueue.next; + while (event != VideoRecordEvent.finalize) { + event = await videoRecordingEventStreamQueue.next; + } + recording = null; + pendingRecording = null; + if (videoOutputPath == null) { - // Stop the current active recording as we will be unable to complete it - // in this error case. - await recording!.close(); - recording = null; - pendingRecording = null; + // Handle any errors with finalizing video recording. throw CameraException( 'INVALID_PATH', 'The platform did not return a path ' 'while reporting success. The platform should always ' 'return a valid path or report an error.'); } - await recording!.close(); - recording = null; - pendingRecording = null; - return XFile(videoOutputPath!); + + await _unbindUseCaseFromLifecycle(videoCapture!); + final XFile videoFile = XFile(videoOutputPath!); + cameraEventStreamController + .add(VideoRecordedEvent(cameraId, videoFile, /* duration */ null)); + return videoFile; } /// Pause the current video recording if it is not null. @@ -975,7 +1056,7 @@ class AndroidCameraCameraX extends CameraPlatform { Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { cameraImageDataStreamController = StreamController( - onListen: () => _configureImageAnalysis(cameraId), + onListen: () async => _configureImageAnalysis(cameraId), onCancel: _onFrameStreamCancel, ); return cameraImageDataStreamController!.stream; @@ -984,26 +1065,32 @@ class AndroidCameraCameraX extends CameraPlatform { // Methods for binding UseCases to the lifecycle of the camera controlled // by a ProcessCameraProvider instance: - /// Binds [preview] instance to the camera lifecycle controlled by the - /// [processCameraProvider]. + /// Binds [useCase] to the camera lifecycle controlled by the + /// [processCameraProvider] if not already bound. /// /// [cameraId] used to build [CameraEvent]s should you wish to filter /// these based on a reference to a cameraId received from calling /// `createCamera(...)`. - Future _bindPreviewToLifecycle(int cameraId) async { - final bool previewIsBound = await processCameraProvider!.isBound(preview!); - if (previewIsBound || _previewIsPaused) { - // Only bind if preview is not already bound or intentionally paused. + Future _bindUseCaseToLifecycle(UseCase useCase, int cameraId) async { + final bool useCaseIsBound = await processCameraProvider!.isBound(useCase); + final bool useCaseIsPausedPreview = useCase is Preview && _previewIsPaused; + + if (useCaseIsBound || useCaseIsPausedPreview) { + // Only bind if useCase is not already bound or preview is intentionally + // paused. return; } camera = await processCameraProvider! - .bindToLifecycle(cameraSelector!, [preview!]); + .bindToLifecycle(cameraSelector!, [useCase]); + await _updateCameraInfoAndLiveCameraState(cameraId); } /// Configures the [imageAnalysis] instance for image streaming. Future _configureImageAnalysis(int cameraId) async { + await _bindUseCaseToLifecycle(imageAnalysis!, cameraId); + // Set target rotation to default CameraX rotation only if capture // orientation not locked. if (!captureOrientationLocked && shouldSetDefaultRotation) { @@ -1044,7 +1131,7 @@ class AndroidCameraCameraX extends CameraPlatform { } /// Unbinds [useCase] from camera lifecycle controlled by the - /// [processCameraProvider]. + /// [processCameraProvider] if not already unbound. Future _unbindUseCaseFromLifecycle(UseCase useCase) async { final bool useCaseIsBound = await processCameraProvider!.isBound(useCase); if (!useCaseIsBound) { @@ -1145,13 +1232,13 @@ class AndroidCameraCameraX extends CameraPlatform { int _getRotationConstantFromDeviceOrientation(DeviceOrientation orientation) { switch (orientation) { case DeviceOrientation.portraitUp: - return Surface.ROTATION_0; + return Surface.rotation0; case DeviceOrientation.landscapeLeft: - return Surface.ROTATION_90; + return Surface.rotation90; case DeviceOrientation.portraitDown: - return Surface.ROTATION_180; + return Surface.rotation180; case DeviceOrientation.landscapeRight: - return Surface.ROTATION_270; + return Surface.rotation270; } } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index db7ec4b32d4..5a2af4e576a 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -4,6 +4,7 @@ import 'analyzer.dart'; import 'camera.dart'; +import 'camera2_camera_info.dart'; import 'camera_control.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; @@ -52,7 +53,8 @@ class AndroidCameraXCameraFlutterApis { PlaneProxyFlutterApiImpl? planeProxyFlutterApiImpl, AnalyzerFlutterApiImpl? analyzerFlutterApiImpl, CameraControlFlutterApiImpl? cameraControlFlutterApiImpl, - FocusMeteringResultFlutterApiImpl? focusMeteringResultFlutterApiImpl}) { + FocusMeteringResultFlutterApiImpl? focusMeteringResultFlutterApiImpl, + Camera2CameraInfoFlutterApiImpl? camera2CameraInfoFlutterApiImpl}) { this.javaObjectFlutterApiImpl = javaObjectFlutterApiImpl ?? JavaObjectFlutterApiImpl(); this.cameraInfoFlutterApiImpl = @@ -99,6 +101,8 @@ class AndroidCameraXCameraFlutterApis { this.focusMeteringResultFlutterApiImpl = focusMeteringResultFlutterApiImpl ?? FocusMeteringResultFlutterApiImpl(); + this.camera2CameraInfoFlutterApiImpl = + camera2CameraInfoFlutterApiImpl ?? Camera2CameraInfoFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -178,6 +182,9 @@ class AndroidCameraXCameraFlutterApis { late final FocusMeteringResultFlutterApiImpl focusMeteringResultFlutterApiImpl; + /// Flutter Api implementation for [Camera2CameraInfo]. + late final Camera2CameraInfoFlutterApiImpl camera2CameraInfoFlutterApiImpl; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -205,6 +212,7 @@ class AndroidCameraXCameraFlutterApis { ObserverFlutterApi.setup(observerFlutterApiImpl); CameraControlFlutterApi.setup(cameraControlFlutterApiImpl); FocusMeteringResultFlutterApi.setup(focusMeteringResultFlutterApiImpl); + Camera2CameraInfoFlutterApi.setup(camera2CameraInfoFlutterApiImpl); _haveBeenSetUp = true; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart new file mode 100644 index 00000000000..fafb90f0ecb --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart @@ -0,0 +1,131 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart' show BinaryMessenger; +import 'package:meta/meta.dart' show immutable; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camera_info.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// Interface for retrieving Camera2-related camera information. +/// +/// See https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraInfo. +@immutable +class Camera2CameraInfo extends JavaObject { + /// Constructs a [Camera2CameraInfo] that is not automatically attached to a native object. + Camera2CameraInfo.detached( + {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = _Camera2CameraInfoHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } + + late final _Camera2CameraInfoHostApiImpl _api; + + /// Retrieves [Camera2CameraInfo] instance from [cameraInfo]. + static Future from(CameraInfo cameraInfo, + {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) { + final _Camera2CameraInfoHostApiImpl api = _Camera2CameraInfoHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + return api.fromInstances(cameraInfo); + } + + /// Retrieves the value of `CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL` + /// for the device to which this instance pertains to. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + /// for more information. + Future getSupportedHardwareLevel() => + _api.getSupportedHardwareLevelFromInstance(this); + + /// Gets the camera ID. + /// + /// The ID may change based on the internal configuration of the camera to which + /// this instances pertains. + Future getCameraId() => _api.getCameraIdFromInstance(this); +} + +/// Host API implementation of [Camera2CameraInfo]. +class _Camera2CameraInfoHostApiImpl extends Camera2CameraInfoHostApi { + /// Constructs a [_Camera2CameraInfoHostApiImpl]. + _Camera2CameraInfoHostApiImpl( + {this.binaryMessenger, InstanceManager? instanceManager}) + : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Maintains instances stored to communicate with native language objects. + late final InstanceManager instanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default [BinaryMessenger] will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Gets sensor orientation degrees of the specified [CameraInfo] instance. + Future fromInstances( + CameraInfo cameraInfo, + ) async { + final int? cameraInfoIdentifier = instanceManager.getIdentifier(cameraInfo); + return instanceManager.getInstanceWithWeakReference( + await createFrom(cameraInfoIdentifier!))!; + } + + Future getSupportedHardwareLevelFromInstance( + Camera2CameraInfo instance) { + final int? identifier = instanceManager.getIdentifier(instance); + return getSupportedHardwareLevel(identifier!); + } + + Future getCameraIdFromInstance(Camera2CameraInfo instance) { + final int? identifier = instanceManager.getIdentifier(instance); + return getCameraId(identifier!); + } +} + +/// Flutter API Implementation of [Camera2CameraInfo]. +class Camera2CameraInfoFlutterApiImpl implements Camera2CameraInfoFlutterApi { + /// Constructs an [Camera2CameraInfoFlutterApiImpl]. + /// + /// If [binaryMessenger] is null, the default [BinaryMessenger] will be used, + /// which routes to the host platform. + /// + /// An [instanceManager] is typically passed when a copy of an instance + /// contained by an [InstanceManager] is being created. If left null, it + /// will default to the global instance defined in [JavaObject]. + Camera2CameraInfoFlutterApiImpl({ + BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + }) : _binaryMessenger = binaryMessenger, + _instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + final BinaryMessenger? _binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager _instanceManager; + + @override + void create(int identifier) { + _instanceManager.addHostCreatedInstance( + Camera2CameraInfo.detached( + binaryMessenger: _binaryMessenger, instanceManager: _instanceManager), + identifier, + onCopy: (Camera2CameraInfo original) { + return Camera2CameraInfo.detached( + binaryMessenger: _binaryMessenger, + instanceManager: _instanceManager); + }, + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart b/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart new file mode 100644 index 00000000000..65098b747c6 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart @@ -0,0 +1,42 @@ +// 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:meta/meta.dart' show immutable; + +/// Base class for camera controls and information. +/// +/// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata. +@immutable +class CameraMetadata { + /// Constant that specifies a camera device does not have enough to quality as + /// a [infoSupportedHardwareLevelFull] level device or better. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED. + static const int infoSupportedHardwareLevelLimited = 0; + + /// Constant that specifies a camera device is capable of supporting advanced + /// imaging applications. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_FULL. + static const int infoSupportedHardwareLevelFull = 1; + + /// Constant that specifies a camera device is running in backward + /// compatibility mode. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY. + static const int infoSupportedHardwareLevelLegacy = 2; + + /// Constant that specifies a camera device is capable of YUV reprocessing and + /// RAW data capture in addition to [infoSupportedHardwareLevelFull] level + /// capabilities. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_3. + static const int infoSupportedHardwareLevel3 = 3; + + /// Constant taht specifies a camera device is backed by an external camera + /// connected to this Android device. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL. + static const int infoSupportedHardwareLevelExternal = 4; +} 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 ac66dfd0ae7..e63a7a6afaf 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 @@ -68,6 +68,14 @@ enum VideoResolutionFallbackRule { lowerQualityThan, } +/// Video recording status. +/// +/// See https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent. +enum VideoRecordEvent { + start, + finalize, +} + /// The types of capture request options this plugin currently supports. /// /// If you need to add another option to support, ensure the following is done @@ -232,6 +240,27 @@ class VideoQualityData { } } +class VideoRecordEventData { + VideoRecordEventData({ + required this.value, + }); + + VideoRecordEvent value; + + Object encode() { + return [ + value.index, + ]; + } + + static VideoRecordEventData decode(Object result) { + result as List; + return VideoRecordEventData( + value: VideoRecordEvent.values[result[0]! as int], + ); + } +} + /// Convenience class for building [FocusMeteringAction]s with multiple metering /// points. class MeteringPointInfo { @@ -1580,11 +1609,36 @@ class PendingRecordingHostApi { } } +class _PendingRecordingFlutterApiCodec extends StandardMessageCodec { + const _PendingRecordingFlutterApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is VideoRecordEventData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return VideoRecordEventData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + abstract class PendingRecordingFlutterApi { - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec codec = _PendingRecordingFlutterApiCodec(); void create(int identifier); + void onVideoRecordingEvent(VideoRecordEventData event); + static void setup(PendingRecordingFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1606,6 +1660,27 @@ abstract class PendingRecordingFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PendingRecordingFlutterApi.onVideoRecordingEvent', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.PendingRecordingFlutterApi.onVideoRecordingEvent was null.'); + final List args = (message as List?)!; + final VideoRecordEventData? arg_event = + (args[0] as VideoRecordEventData?); + assert(arg_event != null, + 'Argument for dev.flutter.pigeon.PendingRecordingFlutterApi.onVideoRecordingEvent was null, expected non-null VideoRecordEventData.'); + api.onVideoRecordingEvent(arg_event!); + return; + }); + } + } } } @@ -3222,6 +3297,9 @@ class _CaptureRequestOptionsHostApiCodec extends StandardMessageCodec { } else if (value is VideoQualityData) { buffer.putUint8(134); writeValue(buffer, value.encode()); + } else if (value is VideoRecordEventData) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -3244,6 +3322,8 @@ class _CaptureRequestOptionsHostApiCodec extends StandardMessageCodec { return ResolutionInfo.decode(readValue(buffer)!); case 134: return VideoQualityData.decode(readValue(buffer)!); + case 135: + return VideoRecordEventData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -3403,3 +3483,125 @@ class ResolutionFilterHostApi { } } } + +class Camera2CameraInfoHostApi { + /// Constructor for [Camera2CameraInfoHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + Camera2CameraInfoHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future createFrom(int arg_cameraInfoIdentifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_cameraInfoIdentifier]) 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 int?)!; + } + } + + Future getSupportedHardwareLevel(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) 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 int?)!; + } + } + + Future getCameraId(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) 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 String?)!; + } + } +} + +abstract class Camera2CameraInfoFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + void create(int identifier); + + static void setup(Camera2CameraInfoFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoFlutterApi.create 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.Camera2CameraInfoFlutterApi.create was null, expected non-null int.'); + api.create(arg_identifier!); + return; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart index 6fec50ce398..d81d1c761c6 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart @@ -7,6 +7,7 @@ import 'dart:ui' show Size; import 'analyzer.dart'; import 'aspect_ratio_strategy.dart'; import 'camera2_camera_control.dart'; +import 'camera2_camera_info.dart'; import 'camera_control.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; @@ -64,6 +65,7 @@ class CameraXProxy { this.createAspectRatioStrategy = _createAttachedAspectRatioStrategy, this.createResolutionFilterWithOnePreferredSize = _createAttachedResolutionFilterWithOnePreferredSize, + this.getCamera2CameraInfo = _getCamera2CameraInfo, }); /// Returns a [ProcessCameraProvider] instance. @@ -154,7 +156,7 @@ class CameraXProxy { /// rotation constants. Future Function() getDefaultDisplayRotation; - /// Get [Camera2CameraControl] instance from [cameraControl]. + /// Gets [Camera2CameraControl] instance from [cameraControl]. Camera2CameraControl Function(CameraControl cameraControl) getCamera2CameraControl; @@ -183,6 +185,10 @@ class CameraXProxy { ResolutionFilter Function(Size preferredResolution) createResolutionFilterWithOnePreferredSize; + /// Gets [Camera2CameraInfo] instance from [cameraInfo]. + Future Function(CameraInfo cameraInfo) + getCamera2CameraInfo; + static Future _getProcessCameraProvider() { return ProcessCameraProvider.getInstance(); } @@ -324,4 +330,9 @@ class CameraXProxy { return ResolutionFilter.onePreferredSize( preferredResolution: preferredSize); } + + static Future _getCamera2CameraInfo( + CameraInfo cameraInfo) async { + return Camera2CameraInfo.from(cameraInfo); + } } diff --git a/packages/camera/camera_android_camerax/lib/src/pending_recording.dart b/packages/camera/camera_android_camerax/lib/src/pending_recording.dart index 971ef49390a..7dcb19e48c5 100644 --- a/packages/camera/camera_android_camerax/lib/src/pending_recording.dart +++ b/packages/camera/camera_android_camerax/lib/src/pending_recording.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/services.dart' show BinaryMessenger; import 'package:meta/meta.dart' show immutable; @@ -30,6 +32,11 @@ class PendingRecording extends JavaObject { late final PendingRecordingHostApiImpl _api; + /// Stream that emits an event when the corresponding video recording is finalized. + static final StreamController + videoRecordingEventStreamController = + StreamController.broadcast(); + /// Starts the recording, making it an active recording. Future start() { return _api.startFromInstance(this); @@ -100,4 +107,9 @@ class PendingRecordingFlutterApiImpl extends PendingRecordingFlutterApi { ); }); } + + @override + void onVideoRecordingEvent(VideoRecordEventData event) { + PendingRecording.videoRecordingEventStreamController.add(event.value); + } } diff --git a/packages/camera/camera_android_camerax/lib/src/surface.dart b/packages/camera/camera_android_camerax/lib/src/surface.dart index e0ca96f639e..925b43bf3c0 100644 --- a/packages/camera/camera_android_camerax/lib/src/surface.dart +++ b/packages/camera/camera_android_camerax/lib/src/surface.dart @@ -18,20 +18,20 @@ class Surface extends JavaObject { /// Rotation constant to signify the natural orientation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_0. - static const int ROTATION_0 = 0; + static const int rotation0 = 0; /// Rotation constant to signify a 90 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_90. - static const int ROTATION_90 = 1; + static const int rotation90 = 1; /// Rotation constant to signify a 180 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_180. - static const int ROTATION_180 = 2; + static const int rotation180 = 2; /// Rotation constant to signify a 270 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_270. - static const int ROTATION_270 = 3; + static const int rotation270 = 3; } diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 18741904a8d..872c3a62239 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -126,6 +126,15 @@ enum VideoResolutionFallbackRule { lowerQualityThan, } +/// Video recording status. +/// +/// See https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent. +enum VideoRecordEvent { start, finalize } + +class VideoRecordEventData { + late VideoRecordEvent value; +} + /// Convenience class for building [FocusMeteringAction]s with multiple metering /// points. class MeteringPointInfo { @@ -325,6 +334,8 @@ abstract class PendingRecordingHostApi { @FlutterApi() abstract class PendingRecordingFlutterApi { void create(int identifier); + + void onVideoRecordingEvent(VideoRecordEventData event); } @HostApi(dartHostTestHandler: 'TestRecordingHostApi') @@ -543,3 +554,17 @@ abstract class ResolutionFilterHostApi { void createWithOnePreferredSize( int identifier, ResolutionInfo preferredResolution); } + +@HostApi(dartHostTestHandler: 'TestCamera2CameraInfoHostApi') +abstract class Camera2CameraInfoHostApi { + int createFrom(int cameraInfoIdentifier); + + int getSupportedHardwareLevel(int identifier); + + String getCameraId(int identifier); +} + +@FlutterApi() +abstract class Camera2CameraInfoFlutterApi { + void create(int identifier); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index b3ce1dfca29..eb94986cbe9 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,11 +2,11 @@ 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.6.3 +version: 0.6.5+6 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: 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 da1bd76b03d..aa33254ac14 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 @@ -11,8 +11,10 @@ import 'package:camera_android_camerax/src/analyzer.dart'; import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart'; import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/camera2_camera_control.dart'; +import 'package:camera_android_camerax/src/camera2_camera_info.dart'; import 'package:camera_android_camerax/src/camera_control.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_metadata.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; import 'package:camera_android_camerax/src/camera_state.dart'; import 'package:camera_android_camerax/src/camera_state_error.dart'; @@ -59,10 +61,12 @@ import 'test_camerax_library.g.dart'; @GenerateNiceMocks(>[ MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -82,10 +86,9 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), - MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), ]) @GenerateMocks([], customMocks: >[ @@ -384,6 +387,8 @@ void main() { createResolutionFilterWithOnePreferredSize: (_) => MockResolutionFilter(), ); + camera.processCameraProvider = mockProcessCameraProvider; + when(mockPreview.setSurfaceProvider()) .thenAnswer((_) async => testSurfaceTextureId); when(mockProcessCameraProvider.bindToLifecycle(mockBackCameraSelector, @@ -392,7 +397,6 @@ void main() { when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => mockLiveCameraState); - camera.processCameraProvider = mockProcessCameraProvider; expect( await camera.createCameraWithSettings( @@ -1259,6 +1263,8 @@ void main() { final MockCameraControl mockCameraControl = MockCameraControl(); final MockLiveCameraState mockLiveCameraState = MockLiveCameraState(); final MockLiveCameraState newMockLiveCameraState = MockLiveCameraState(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); final TestSystemServicesHostApi mockSystemServicesApi = MockTestSystemServicesHostApi(); TestSystemServicesHostApi.setup(mockSystemServicesApi); @@ -1270,6 +1276,8 @@ void main() { camera.videoCapture = MockVideoCapture(); camera.cameraSelector = MockCameraSelector(); camera.liveCameraState = mockLiveCameraState; + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -1277,10 +1285,12 @@ void main() { // Tell plugin to create detached Observer when camera info updated. camera.proxy = CameraXProxy( createCameraStateObserver: (void Function(Object) onChanged) => - Observer.detached(onChanged: onChanged)); + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); const int cameraId = 17; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -1299,6 +1309,12 @@ void main() { .thenAnswer((_) async => mockCameraControl); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => newMockLiveCameraState); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelLimited); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); @@ -1332,6 +1348,8 @@ void main() { final MockRecording mockRecording = MockRecording(); final MockCamera mockCamera = MockCamera(); final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); final TestSystemServicesHostApi mockSystemServicesApi = MockTestSystemServicesHostApi(); TestSystemServicesHostApi.setup(mockSystemServicesApi); @@ -1341,6 +1359,8 @@ void main() { camera.recorder = MockRecorder(); camera.videoCapture = MockVideoCapture(); camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -1348,10 +1368,12 @@ void main() { // Tell plugin to create detached Observer when camera info updated. camera.proxy = CameraXProxy( createCameraStateObserver: (void Function(Object) onChanged) => - Observer.detached(onChanged: onChanged)); + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); const int cameraId = 17; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -1368,6 +1390,12 @@ void main() { .thenAnswer((_) => Future.value(mockCameraInfo)); when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelLimited); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); @@ -1392,21 +1420,27 @@ void main() { () async { // Set up mocks and constants. final AndroidCameraCameraX camera = AndroidCameraCameraX(); - - // Set directly for test versus calling createCamera. final MockProcessCameraProvider mockProcessCameraProvider = MockProcessCameraProvider(); + final Recorder mockRecorder = MockRecorder(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockCameraInfo initialCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = mockProcessCameraProvider; camera.cameraSelector = MockCameraSelector(); camera.videoCapture = MockVideoCapture(); camera.imageAnalysis = MockImageAnalysis(); camera.camera = MockCamera(); - final Recorder mockRecorder = MockRecorder(); camera.recorder = mockRecorder; - final MockPendingRecording mockPendingRecording = MockPendingRecording(); - final TestSystemServicesHostApi mockSystemServicesApi = - MockTestSystemServicesHostApi(); - TestSystemServicesHostApi.setup(mockSystemServicesApi); + camera.cameraInfo = initialCameraInfo; + camera.imageCapture = MockImageCapture(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -1415,10 +1449,14 @@ void main() { camera.proxy = CameraXProxy( createAnalyzer: (Future Function(ImageProxy imageProxy) analyze) => - Analyzer.detached(analyze: analyze)); + Analyzer.detached(analyze: analyze), + getCamera2CameraInfo: (CameraInfo cameraInfo) async => + cameraInfo == initialCameraInfo + ? mockCamera2CameraInfo + : MockCamera2CameraInfo()); const int cameraId = 17; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; final Completer imageDataCompleter = Completer(); final VideoCaptureOptions videoCaptureOptions = VideoCaptureOptions( @@ -1429,6 +1467,8 @@ void main() { // Mock method calls. when(camera.processCameraProvider!.isBound(camera.videoCapture!)) .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) .thenReturn(outputPath); when(camera.recorder!.prepareRecording(outputPath)) @@ -1437,6 +1477,12 @@ void main() { .thenAnswer((_) => Future.value(camera.camera)); when(camera.camera!.getCameraInfo()) .thenAnswer((_) => Future.value(MockCameraInfo())); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); await camera.startVideoCapturing(videoCaptureOptions); @@ -1455,10 +1501,13 @@ void main() { final MockPendingRecording mockPendingRecording = MockPendingRecording(); final MockRecording mockRecording = MockRecording(); final MockVideoCapture mockVideoCapture = MockVideoCapture(); + final MockCameraInfo initialCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = + MockCamera2CameraInfo(); final TestSystemServicesHostApi mockSystemServicesApi = MockTestSystemServicesHostApi(); TestSystemServicesHostApi.setup(mockSystemServicesApi); - const int defaultTargetRotation = Surface.ROTATION_270; + const int defaultTargetRotation = Surface.rotation270; // Set directly for test versus calling createCamera. camera.processCameraProvider = MockProcessCameraProvider(); @@ -1466,14 +1515,20 @@ void main() { camera.recorder = MockRecorder(); camera.videoCapture = mockVideoCapture; camera.cameraSelector = MockCameraSelector(); + camera.imageAnalysis = MockImageAnalysis(); + camera.cameraInfo = initialCameraInfo; - // Tell plugin to mock call to get current video orientation. + // Tell plugin to mock call to get current video orientation and mock Camera2CameraInfo retrieval. camera.proxy = CameraXProxy( getDefaultDisplayRotation: () => - Future.value(defaultTargetRotation)); + Future.value(defaultTargetRotation), + getCamera2CameraInfo: (CameraInfo cameraInfo) async => + cameraInfo == initialCameraInfo + ? mockCamera2CameraInfo + : MockCamera2CameraInfo()); const int cameraId = 87; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -1483,6 +1538,12 @@ void main() { when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); when(camera.processCameraProvider!.isBound(camera.videoCapture!)) .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => false); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); // Orientation is unlocked and plugin does not need to set default target // rotation manually. @@ -1490,6 +1551,10 @@ void main() { await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); verifyNever(mockVideoCapture.setTargetRotation(any)); + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + // Orientation is locked and plugin does not need to set default target // rotation manually. camera.recording = null; @@ -1497,6 +1562,10 @@ void main() { await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); verifyNever(mockVideoCapture.setTargetRotation(any)); + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + // Orientation is locked and plugin does need to set default target // rotation manually. camera.recording = null; @@ -1505,6 +1574,10 @@ void main() { await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); verifyNever(mockVideoCapture.setTargetRotation(any)); + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + // Orientation is unlocked and plugin does need to set default target // rotation manually. camera.recording = null; @@ -1556,6 +1629,10 @@ void main() { when(camera.processCameraProvider!.isBound(videoCapture)) .thenAnswer((_) async => true); + // Simulate video recording being finalized so stopVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.finalize); + final XFile file = await camera.stopVideoRecording(0); expect(file.path, videoOutputPath); @@ -1578,67 +1655,147 @@ void main() { await camera.stopVideoRecording(0); }, throwsA(isA())); }); - }); - test( - 'stopVideoRecording throws a camera exception if ' - 'videoOutputPath is null, and sets recording to null', () async { - final AndroidCameraCameraX camera = AndroidCameraCameraX(); - final MockRecording mockRecording = MockRecording(); - final MockVideoCapture mockVideoCapture = MockVideoCapture(); + test( + 'stopVideoRecording throws a camera exception if ' + 'videoOutputPath is null, and sets recording to null', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockRecording mockRecording = MockRecording(); + final MockVideoCapture mockVideoCapture = MockVideoCapture(); - // Set directly for test versus calling startVideoCapturing. - camera.processCameraProvider = MockProcessCameraProvider(); - camera.recording = mockRecording; - camera.videoOutputPath = null; - camera.videoCapture = mockVideoCapture; + // Set directly for test versus calling startVideoCapturing. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recording = mockRecording; + camera.videoOutputPath = null; + camera.videoCapture = mockVideoCapture; - // Tell plugin that videoCapture use case was bound to start recording. - when(camera.processCameraProvider!.isBound(mockVideoCapture)) - .thenAnswer((_) async => true); + // Tell plugin that videoCapture use case was bound to start recording. + when(camera.processCameraProvider!.isBound(mockVideoCapture)) + .thenAnswer((_) async => true); - await expectLater(() async { - await camera.stopVideoRecording(0); - }, throwsA(isA())); - expect(camera.recording, null); - }); + await expectLater(() async { + // Simulate video recording being finalized so stopVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.finalize); + await camera.stopVideoRecording(0); + }, throwsA(isA())); + expect(camera.recording, null); + }); - test( - 'calling stopVideoRecording twice stops the recording ' - 'and then throws a CameraException', () async { - final AndroidCameraCameraX camera = AndroidCameraCameraX(); - final MockRecording recording = MockRecording(); - final MockProcessCameraProvider processCameraProvider = - MockProcessCameraProvider(); - final MockVideoCapture videoCapture = MockVideoCapture(); - const String videoOutputPath = '/test/output/path'; + test( + 'calling stopVideoRecording twice stops the recording ' + 'and then throws a CameraException', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockRecording recording = MockRecording(); + final MockProcessCameraProvider processCameraProvider = + MockProcessCameraProvider(); + final MockVideoCapture videoCapture = MockVideoCapture(); + const String videoOutputPath = '/test/output/path'; - // Set directly for test versus calling createCamera and startVideoCapturing. - camera.processCameraProvider = processCameraProvider; - camera.recording = recording; - camera.videoCapture = videoCapture; - camera.videoOutputPath = videoOutputPath; + // Set directly for test versus calling createCamera and startVideoCapturing. + camera.processCameraProvider = processCameraProvider; + camera.recording = recording; + camera.videoCapture = videoCapture; + camera.videoOutputPath = videoOutputPath; - final XFile file = await camera.stopVideoRecording(0); - expect(file.path, videoOutputPath); + // Simulate video recording being finalized so stopVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.finalize); - await expectLater(() async { - await camera.stopVideoRecording(0); - }, throwsA(isA())); + final XFile file = await camera.stopVideoRecording(0); + expect(file.path, videoOutputPath); + + await expectLater(() async { + await camera.stopVideoRecording(0); + }, throwsA(isA())); + }); + + test( + 'VideoCapture use case is unbound from lifecycle when video recording stops', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockRecording recording = MockRecording(); + final MockProcessCameraProvider processCameraProvider = + MockProcessCameraProvider(); + final MockVideoCapture videoCapture = MockVideoCapture(); + const String videoOutputPath = '/test/output/path'; + + // Set directly for test versus calling createCamera and startVideoCapturing. + camera.processCameraProvider = processCameraProvider; + camera.recording = recording; + camera.videoCapture = videoCapture; + camera.videoOutputPath = videoOutputPath; + + // Tell plugin that videoCapture use case was bound to start recording. + when(camera.processCameraProvider!.isBound(videoCapture)) + .thenAnswer((_) async => true); + + // Simulate video recording being finalized so stopVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.finalize); + + await camera.stopVideoRecording(90); + verify(processCameraProvider.unbind([videoCapture])); + + // Verify that recording stops. + verify(recording.close()); + verifyNoMoreInteractions(recording); + }); + + test( + 'setDescriptionWhileRecording does not make any calls involving starting video recording', + () async { + // TODO(camsim99): Modify test when implemented, see https://github.com/flutter/flutter/issues/148013. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.camera = MockCamera(); + + await camera.setDescriptionWhileRecording(const CameraDescription( + name: 'fakeCameraName', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90)); + verifyNoMoreInteractions(camera.processCameraProvider); + verifyNoMoreInteractions(camera.recorder); + verifyNoMoreInteractions(camera.videoCapture); + verifyNoMoreInteractions(camera.camera); + }); }); test( - 'takePicture binds and unbinds ImageCapture to lifecycle and makes call to take a picture', + 'takePicture binds ImageCapture to lifecycle and makes call to take a picture', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); const String testPicturePath = 'test/absolute/path/to/picture'; // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = MockCameraSelector(); // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; + // Tell plugin to create detached camera state observers. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged)); + + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => false); + when(mockProcessCameraProvider.bindToLifecycle( + camera.cameraSelector, [camera.imageCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); when(camera.imageCapture!.takePicture()) .thenAnswer((_) async => testPicturePath); @@ -1652,17 +1809,23 @@ void main() { () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); final MockImageCapture mockImageCapture = MockImageCapture(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + const int cameraId = 3; - const int defaultTargetRotation = Surface.ROTATION_180; + const int defaultTargetRotation = Surface.rotation180; // Set directly for test versus calling createCamera. camera.imageCapture = mockImageCapture; + camera.processCameraProvider = mockProcessCameraProvider; // Tell plugin to mock call to get current photo orientation. camera.proxy = CameraXProxy( getDefaultDisplayRotation: () => Future.value(defaultTargetRotation)); + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => true); when(camera.imageCapture!.takePicture()) .thenAnswer((_) async => 'test/absolute/path/to/picture'); @@ -1695,15 +1858,21 @@ void main() { test('takePicture turns non-torch flash mode off when torch mode enabled', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); const int cameraId = 77; // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); camera.cameraControl = MockCameraControl(); + camera.processCameraProvider = mockProcessCameraProvider; // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => true); + await camera.setFlashMode(cameraId, FlashMode.torch); await camera.takePicture(cameraId); verify(camera.imageCapture!.setFlashMode(ImageCapture.flashModeOff)); @@ -1715,6 +1884,8 @@ void main() { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 22; final MockCameraControl mockCameraControl = MockCameraControl(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); // Set directly for test versus calling createCamera. camera.imageCapture = MockImageCapture(); @@ -1722,6 +1893,10 @@ void main() { // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; + camera.processCameraProvider = mockProcessCameraProvider; + + when(mockProcessCameraProvider.isBound(camera.imageCapture)) + .thenAnswer((_) async => true); for (final FlashMode flashMode in FlashMode.values) { await camera.setFlashMode(cameraId, flashMode); @@ -1939,6 +2114,8 @@ void main() { when(mockProcessCameraProvider.bindToLifecycle(any, any)) .thenAnswer((_) => Future.value(mockCamera)); + when(mockProcessCameraProvider.isBound(camera.imageAnalysis)) + .thenAnswer((_) async => true); when(mockCamera.getCameraInfo()) .thenAnswer((_) => Future.value(mockCameraInfo)); when(mockCameraInfo.getCameraState()) @@ -1962,8 +2139,6 @@ void main() { final AndroidCameraCameraX camera = AndroidCameraCameraX(); final MockProcessCameraProvider mockProcessCameraProvider = MockProcessCameraProvider(); - final MockCamera mockCamera = MockCamera(); - final MockCameraInfo mockCameraInfo = MockCameraInfo(); const int cameraId = 22; // Tell plugin to create detached Analyzer for testing. @@ -1980,12 +2155,8 @@ void main() { // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; - when(mockProcessCameraProvider.bindToLifecycle(any, any)) - .thenAnswer((_) => Future.value(mockCamera)); - when(mockCamera.getCameraInfo()) - .thenAnswer((_) => Future.value(mockCameraInfo)); - when(mockCameraInfo.getCameraState()) - .thenAnswer((_) async => MockLiveCameraState()); + when(mockProcessCameraProvider.isBound(camera.imageAnalysis)) + .thenAnswer((_) async => true); final CameraImageData mockCameraImageData = MockCameraImageData(); final Stream imageStream = @@ -2034,7 +2205,9 @@ void main() { camera.proxy = CameraXProxy( createAnalyzer: (Future Function(ImageProxy imageProxy) analyze) => - Analyzer.detached(analyze: analyze)); + Analyzer.detached(analyze: analyze), + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged)); // Set directly for test versus calling createCamera. camera.processCameraProvider = mockProcessCameraProvider; @@ -2045,7 +2218,7 @@ void main() { camera.captureOrientationLocked = true; when(mockProcessCameraProvider.isBound(mockImageAnalysis)) - .thenAnswer((_) async => Future.value(false)); + .thenAnswer((_) async => false); when(mockProcessCameraProvider .bindToLifecycle(mockCameraSelector, [mockImageAnalysis])) .thenAnswer((_) async => mockCamera); @@ -2053,7 +2226,7 @@ void main() { when(mockCameraInfo.getCameraState()) .thenAnswer((_) async => MockLiveCameraState()); when(mockImageProxy.getPlanes()) - .thenAnswer((_) => Future>.value(mockPlanes)); + .thenAnswer((_) async => Future>.value(mockPlanes)); when(mockPlane.buffer).thenReturn(buffer); when(mockPlane.rowStride).thenReturn(rowStride); when(mockPlane.pixelStride).thenReturn(pixelStride); @@ -2071,6 +2244,7 @@ void main() { }); // Test ImageAnalysis use case is bound to ProcessCameraProvider. + await untilCalled(mockImageAnalysis.setAnalyzer(any)); final Analyzer capturedAnalyzer = verify(mockImageAnalysis.setAnalyzer(captureAny)).captured.single as Analyzer; @@ -2097,9 +2271,12 @@ void main() { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 32; final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); // Set directly for test versus calling createCamera. camera.imageAnalysis = mockImageAnalysis; + camera.processCameraProvider = mockProcessCameraProvider; // Ignore setting target rotation for this test; tested seprately. camera.captureOrientationLocked = true; @@ -2107,6 +2284,9 @@ void main() { // Tell plugin to create a detached analyzer for testing purposes. camera.proxy = CameraXProxy(createAnalyzer: (_) => MockAnalyzer()); + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) + .thenAnswer((_) async => true); + final StreamSubscription imageStreamSubscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData data) {}); @@ -2121,11 +2301,14 @@ void main() { () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 35; - const int defaultTargetRotation = Surface.ROTATION_90; + const int defaultTargetRotation = Surface.rotation90; final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); // Set directly for test versus calling createCamera. camera.imageAnalysis = mockImageAnalysis; + camera.processCameraProvider = mockProcessCameraProvider; // Tell plugin to create a detached analyzer for testing purposes and mock // call to get current photo orientation. @@ -2134,6 +2317,9 @@ void main() { getDefaultDisplayRotation: () => Future.value(defaultTargetRotation)); + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) + .thenAnswer((_) async => true); + // Orientation is unlocked and plugin does not need to set default target // rotation manually. StreamSubscription imageStreamSubscription = camera @@ -2195,13 +2381,13 @@ void main() { int? expectedTargetRotation; switch (orientation) { case DeviceOrientation.portraitUp: - expectedTargetRotation = Surface.ROTATION_0; + expectedTargetRotation = Surface.rotation0; case DeviceOrientation.landscapeLeft: - expectedTargetRotation = Surface.ROTATION_90; + expectedTargetRotation = Surface.rotation90; case DeviceOrientation.portraitDown: - expectedTargetRotation = Surface.ROTATION_180; + expectedTargetRotation = Surface.rotation180; case DeviceOrientation.landscapeRight: - expectedTargetRotation = Surface.ROTATION_270; + expectedTargetRotation = Surface.rotation270; } await camera.lockCaptureOrientation(cameraId, orientation); @@ -3496,4 +3682,470 @@ void main() { verificationResult.captured.single as FocusMeteringAction; expect(capturedAction.disableAutoCancel, isFalse); }); + + test( + 'onStreamedFrameAvailable binds ImageAnalysis use case when not already bound', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 22; + final MockImageAnalysis mockImageAnalysis = MockImageAnalysis(); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + + // Set directly for test versus calling createCamera. + camera.imageAnalysis = mockImageAnalysis; + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = MockCameraSelector(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create a detached analyzer for testing purposes. + camera.proxy = CameraXProxy( + createAnalyzer: (_) => MockAnalyzer(), + createCameraStateObserver: (_) => MockObserver(), + ); + + when(mockProcessCameraProvider.isBound(mockImageAnalysis)) + .thenAnswer((_) async => false); + when(mockProcessCameraProvider.bindToLifecycle( + any, [mockImageAnalysis])).thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + + final StreamSubscription imageStreamSubscription = camera + .onStreamedFrameAvailable(cameraId) + .listen((CameraImageData data) {}); + + await untilCalled(mockImageAnalysis.setAnalyzer(any)); + verify(mockProcessCameraProvider + .bindToLifecycle(camera.cameraSelector, [mockImageAnalysis])); + + await imageStreamSubscription.cancel(); + }); + + test( + 'startVideoCapturing unbinds ImageAnalysis use case when camera device is not at least level 3, no image streaming callback is specified, and preview is not paused', + () async { + // Set up mocks and constants. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockRecording mockRecording = MockRecording(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create detached Observer when camera info updated. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 7; + const String outputPath = '/temp/REC123.temp'; + + // Mock method calls. + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.recorder!.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => false); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevelFull); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback not specified, camera device is level 3, and preview is not paused', + () async { + // Set up mocks and constants. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockRecording mockRecording = MockRecording(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create detached Observer when camera info updated. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 77; + const String outputPath = '/temp/REC123.temp'; + + // Mock method calls. + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.recorder!.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => false); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback is specified, camera device is not at least level 3, and preview is not paused', + () async { + // Set up mocks and constants. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockRecording mockRecording = MockRecording(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create detached Observer when camera info updated. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 87; + const String outputPath = '/temp/REC123.temp'; + + // Mock method calls. + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.recorder!.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => false); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelExternal); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + + await camera.startVideoCapturing(VideoCaptureOptions(cameraId, + streamCallback: (CameraImageData image) {})); + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageCapture use case when image streaming callback is specified, camera device is at least level 3, and preview is not paused', + () async { + // Set up mocks and constants. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockRecording mockRecording = MockRecording(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + camera.imageCapture = MockImageCapture(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create detached Observer when camera info updated. + camera.proxy = CameraXProxy( + createAnalyzer: + (Future Function(ImageProxy imageProxy) analyze) => + Analyzer.detached(analyze: analyze), + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 107; + const String outputPath = '/temp/REC123.temp'; + + // Mock method calls. + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.recorder!.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => false); + when(camera.processCameraProvider!.isBound(camera.imageCapture!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()) + .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + + await camera.startVideoCapturing(VideoCaptureOptions(cameraId, + streamCallback: (CameraImageData image) {})); + verify( + camera.processCameraProvider!.unbind([camera.imageCapture!])); + }); + + test( + 'startVideoCapturing does not unbind ImageCapture or ImageAnalysis use cases when preview is paused', + () async { + // Set up mocks and constants. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockRecording mockRecording = MockRecording(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + camera.imageCapture = MockImageCapture(); + camera.preview = MockPreview(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create detached Observer when camera info updated. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 97; + const String outputPath = '/temp/REC123.temp'; + + // Mock method calls. + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.recorder!.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => false); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + + await camera.pausePreview(cameraId); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verifyNever( + camera.processCameraProvider!.unbind([camera.imageCapture!])); + verifyNever( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'startVideoCapturing unbinds ImageCapture and ImageAnalysis use cases when running on a legacy hardware device', + () async { + // Set up mocks and constants. + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final MockRecording mockRecording = MockRecording(); + final MockCamera mockCamera = MockCamera(); + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.cameraSelector = MockCameraSelector(); + camera.cameraInfo = MockCameraInfo(); + camera.imageAnalysis = MockImageAnalysis(); + camera.imageCapture = MockImageCapture(); + camera.preview = MockPreview(); + + // Ignore setting target rotation for this test; tested seprately. + camera.captureOrientationLocked = true; + + // Tell plugin to create detached Observer when camera info updated. + camera.proxy = CameraXProxy( + createCameraStateObserver: (void Function(Object) onChanged) => + Observer.detached(onChanged: onChanged), + getCamera2CameraInfo: (CameraInfo cameraInfo) => + Future.value(mockCamera2CameraInfo)); + + const int cameraId = 44; + const String outputPath = '/temp/REC123.temp'; + + // Mock method calls. + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.recorder!.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording); + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => false); + when(camera.processCameraProvider!.isBound(camera.imageCapture!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.isBound(camera.imageAnalysis!)) + .thenAnswer((_) async => true); + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.videoCapture!])) + .thenAnswer((_) async => mockCamera); + when(mockCamera.getCameraInfo()) + .thenAnswer((_) => Future.value(mockCameraInfo)); + when(mockCameraInfo.getCameraState()) + .thenAnswer((_) async => MockLiveCameraState()); + when(mockCamera2CameraInfo.getSupportedHardwareLevel()).thenAnswer( + (_) async => CameraMetadata.infoSupportedHardwareLevelLegacy); + + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); + + verify( + camera.processCameraProvider!.unbind([camera.imageCapture!])); + verify( + camera.processCameraProvider!.unbind([camera.imageAnalysis!])); + }); + + test( + 'prepareForVideoRecording does not make any calls involving starting video recording', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + // Set directly for test versus calling createCamera. + camera.processCameraProvider = MockProcessCameraProvider(); + camera.recorder = MockRecorder(); + camera.videoCapture = MockVideoCapture(); + camera.camera = MockCamera(); + + await camera.prepareForVideoRecording(); + verifyNoMoreInteractions(camera.processCameraProvider); + verifyNoMoreInteractions(camera.recorder); + verifyNoMoreInteractions(camera.videoCapture); + verifyNoMoreInteractions(camera.camera); + }); } 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 5d6d2051212..0bdfc06af92 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 @@ -4,50 +4,51 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i17; -import 'dart:typed_data' as _i33; -import 'dart:ui' as _i11; +import 'dart:typed_data' as _i34; +import 'dart:ui' as _i14; import 'package:camera_android_camerax/src/analyzer.dart' as _i16; import 'package:camera_android_camerax/src/aspect_ratio_strategy.dart' as _i19; -import 'package:camera_android_camerax/src/camera.dart' as _i9; +import 'package:camera_android_camerax/src/camera.dart' as _i12; import 'package:camera_android_camerax/src/camera2_camera_control.dart' as _i24; -import 'package:camera_android_camerax/src/camera_control.dart' as _i3; -import 'package:camera_android_camerax/src/camera_info.dart' as _i2; -import 'package:camera_android_camerax/src/camera_selector.dart' as _i26; +import 'package:camera_android_camerax/src/camera2_camera_info.dart' as _i26; +import 'package:camera_android_camerax/src/camera_control.dart' as _i6; +import 'package:camera_android_camerax/src/camera_info.dart' as _i5; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i28; import 'package:camera_android_camerax/src/camera_state.dart' as _i20; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i10; import 'package:camera_android_camerax/src/capture_request_options.dart' as _i25; -import 'package:camera_android_camerax/src/exposure_state.dart' as _i5; -import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i27; +import 'package:camera_android_camerax/src/exposure_state.dart' as _i8; +import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i29; import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i23; import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i22; -import 'package:camera_android_camerax/src/image_analysis.dart' as _i28; -import 'package:camera_android_camerax/src/image_capture.dart' as _i29; +import 'package:camera_android_camerax/src/image_analysis.dart' as _i30; +import 'package:camera_android_camerax/src/image_capture.dart' as _i31; import 'package:camera_android_camerax/src/image_proxy.dart' as _i18; -import 'package:camera_android_camerax/src/live_data.dart' as _i4; -import 'package:camera_android_camerax/src/observer.dart' as _i32; -import 'package:camera_android_camerax/src/pending_recording.dart' as _i10; -import 'package:camera_android_camerax/src/plane_proxy.dart' as _i31; -import 'package:camera_android_camerax/src/preview.dart' as _i34; +import 'package:camera_android_camerax/src/live_data.dart' as _i7; +import 'package:camera_android_camerax/src/observer.dart' as _i33; +import 'package:camera_android_camerax/src/pending_recording.dart' as _i13; +import 'package:camera_android_camerax/src/plane_proxy.dart' as _i32; +import 'package:camera_android_camerax/src/preview.dart' as _i35; import 'package:camera_android_camerax/src/process_camera_provider.dart' - as _i35; -import 'package:camera_android_camerax/src/quality_selector.dart' as _i37; -import 'package:camera_android_camerax/src/recorder.dart' as _i12; -import 'package:camera_android_camerax/src/recording.dart' as _i8; -import 'package:camera_android_camerax/src/resolution_filter.dart' as _i38; -import 'package:camera_android_camerax/src/resolution_selector.dart' as _i39; -import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i40; -import 'package:camera_android_camerax/src/use_case.dart' as _i36; -import 'package:camera_android_camerax/src/video_capture.dart' as _i41; + as _i36; +import 'package:camera_android_camerax/src/quality_selector.dart' as _i38; +import 'package:camera_android_camerax/src/recorder.dart' as _i15; +import 'package:camera_android_camerax/src/recording.dart' as _i11; +import 'package:camera_android_camerax/src/resolution_filter.dart' as _i39; +import 'package:camera_android_camerax/src/resolution_selector.dart' as _i40; +import 'package:camera_android_camerax/src/resolution_strategy.dart' as _i41; +import 'package:camera_android_camerax/src/use_case.dart' as _i37; +import 'package:camera_android_camerax/src/video_capture.dart' as _i43; import 'package:camera_android_camerax/src/zoom_state.dart' as _i21; import 'package:camera_platform_interface/camera_platform_interface.dart' - as _i6; -import 'package:flutter/foundation.dart' as _i15; -import 'package:flutter/services.dart' as _i14; -import 'package:flutter/widgets.dart' as _i13; + as _i9; +import 'package:flutter/foundation.dart' as _i4; +import 'package:flutter/services.dart' as _i3; +import 'package:flutter/widgets.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i30; +import 'package:mockito/src/dummies.dart' as _i27; import 'test_camerax_library.g.dart' as _i42; @@ -64,39 +65,55 @@ import 'test_camerax_library.g.dart' as _i42; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeCameraInfo_0 extends _i1.SmartFake implements _i2.CameraInfo { - _FakeCameraInfo_0( +class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { + _FakeWidget_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); } -class _FakeCameraControl_1 extends _i1.SmartFake implements _i3.CameraControl { - _FakeCameraControl_1( +class _FakeInheritedWidget_1 extends _i1.SmartFake + implements _i2.InheritedWidget { + _FakeInheritedWidget_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); } -class _FakeLiveData_2 extends _i1.SmartFake - implements _i4.LiveData { - _FakeLiveData_2( +class _FakeDiagnosticsNode_2 extends _i1.SmartFake + implements _i4.DiagnosticsNode { + _FakeDiagnosticsNode_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); + + @override + String toString({ + _i4.TextTreeConfiguration? parentConfiguration, + _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, + }) => + super.toString(); } -class _FakeExposureState_3 extends _i1.SmartFake implements _i5.ExposureState { - _FakeExposureState_3( +class _FakeCameraInfo_3 extends _i1.SmartFake implements _i5.CameraInfo { + _FakeCameraInfo_3( Object parent, Invocation parentInvocation, ) : super( @@ -105,9 +122,8 @@ class _FakeExposureState_3 extends _i1.SmartFake implements _i5.ExposureState { ); } -class _FakeCameraImageFormat_4 extends _i1.SmartFake - implements _i6.CameraImageFormat { - _FakeCameraImageFormat_4( +class _FakeCameraControl_4 extends _i1.SmartFake implements _i6.CameraControl { + _FakeCameraControl_4( Object parent, Invocation parentInvocation, ) : super( @@ -116,9 +132,9 @@ class _FakeCameraImageFormat_4 extends _i1.SmartFake ); } -class _FakeExposureCompensationRange_5 extends _i1.SmartFake - implements _i7.ExposureCompensationRange { - _FakeExposureCompensationRange_5( +class _FakeLiveData_5 extends _i1.SmartFake + implements _i7.LiveData { + _FakeLiveData_5( Object parent, Invocation parentInvocation, ) : super( @@ -127,8 +143,8 @@ class _FakeExposureCompensationRange_5 extends _i1.SmartFake ); } -class _FakeRecording_6 extends _i1.SmartFake implements _i8.Recording { - _FakeRecording_6( +class _FakeExposureState_6 extends _i1.SmartFake implements _i8.ExposureState { + _FakeExposureState_6( Object parent, Invocation parentInvocation, ) : super( @@ -137,9 +153,9 @@ class _FakeRecording_6 extends _i1.SmartFake implements _i8.Recording { ); } -class _FakeResolutionInfo_7 extends _i1.SmartFake - implements _i7.ResolutionInfo { - _FakeResolutionInfo_7( +class _FakeCameraImageFormat_7 extends _i1.SmartFake + implements _i9.CameraImageFormat { + _FakeCameraImageFormat_7( Object parent, Invocation parentInvocation, ) : super( @@ -148,8 +164,9 @@ class _FakeResolutionInfo_7 extends _i1.SmartFake ); } -class _FakeCamera_8 extends _i1.SmartFake implements _i9.Camera { - _FakeCamera_8( +class _FakeExposureCompensationRange_8 extends _i1.SmartFake + implements _i10.ExposureCompensationRange { + _FakeExposureCompensationRange_8( Object parent, Invocation parentInvocation, ) : super( @@ -158,9 +175,8 @@ class _FakeCamera_8 extends _i1.SmartFake implements _i9.Camera { ); } -class _FakePendingRecording_9 extends _i1.SmartFake - implements _i10.PendingRecording { - _FakePendingRecording_9( +class _FakeRecording_9 extends _i1.SmartFake implements _i11.Recording { + _FakeRecording_9( Object parent, Invocation parentInvocation, ) : super( @@ -169,8 +185,9 @@ class _FakePendingRecording_9 extends _i1.SmartFake ); } -class _FakeSize_10 extends _i1.SmartFake implements _i11.Size { - _FakeSize_10( +class _FakeResolutionInfo_10 extends _i1.SmartFake + implements _i10.ResolutionInfo { + _FakeResolutionInfo_10( Object parent, Invocation parentInvocation, ) : super( @@ -179,8 +196,8 @@ class _FakeSize_10 extends _i1.SmartFake implements _i11.Size { ); } -class _FakeRecorder_11 extends _i1.SmartFake implements _i12.Recorder { - _FakeRecorder_11( +class _FakeCamera_11 extends _i1.SmartFake implements _i12.Camera { + _FakeCamera_11( Object parent, Invocation parentInvocation, ) : super( @@ -189,53 +206,35 @@ class _FakeRecorder_11 extends _i1.SmartFake implements _i12.Recorder { ); } -class _FakeWidget_12 extends _i1.SmartFake implements _i13.Widget { - _FakeWidget_12( +class _FakePendingRecording_12 extends _i1.SmartFake + implements _i13.PendingRecording { + _FakePendingRecording_12( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); - - @override - String toString( - {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => - super.toString(); } -class _FakeInheritedWidget_13 extends _i1.SmartFake - implements _i13.InheritedWidget { - _FakeInheritedWidget_13( +class _FakeSize_13 extends _i1.SmartFake implements _i14.Size { + _FakeSize_13( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); - - @override - String toString( - {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => - super.toString(); } -class _FakeDiagnosticsNode_14 extends _i1.SmartFake - implements _i15.DiagnosticsNode { - _FakeDiagnosticsNode_14( +class _FakeRecorder_14 extends _i1.SmartFake implements _i15.Recorder { + _FakeRecorder_14( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); - - @override - String toString({ - _i15.TextTreeConfiguration? parentConfiguration, - _i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info, - }) => - super.toString(); } /// A class which mocks [Analyzer]. @@ -274,18 +273,202 @@ class MockAspectRatioStrategy extends _i1.Mock ) as int); } +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i2.BuildContext { + @override + _i2.Widget get widget => (super.noSuchMethod( + Invocation.getter(#widget), + returnValue: _FakeWidget_0( + this, + Invocation.getter(#widget), + ), + returnValueForMissingStub: _FakeWidget_0( + this, + Invocation.getter(#widget), + ), + ) as _i2.Widget); + + @override + bool get mounted => (super.noSuchMethod( + Invocation.getter(#mounted), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + bool get debugDoingBuild => (super.noSuchMethod( + Invocation.getter(#debugDoingBuild), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i2.InheritedWidget dependOnInheritedElement( + _i2.InheritedElement? ancestor, { + Object? aspect, + }) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + returnValue: _FakeInheritedWidget_1( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + returnValueForMissingStub: _FakeInheritedWidget_1( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + ) as _i2.InheritedWidget); + + @override + void visitAncestorElements(_i2.ConditionalElementVisitor? visitor) => + super.noSuchMethod( + Invocation.method( + #visitAncestorElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + + @override + void visitChildElements(_i2.ElementVisitor? visitor) => super.noSuchMethod( + Invocation.method( + #visitChildElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + + @override + void dispatchNotification(_i2.Notification? notification) => + super.noSuchMethod( + Invocation.method( + #dispatchNotification, + [notification], + ), + returnValueForMissingStub: null, + ); + + @override + _i4.DiagnosticsNode describeElement( + String? name, { + _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + returnValueForMissingStub: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + ) as _i4.DiagnosticsNode); + + @override + _i4.DiagnosticsNode describeWidget( + String? name, { + _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + returnValueForMissingStub: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + ) as _i4.DiagnosticsNode); + + @override + List<_i4.DiagnosticsNode> describeMissingAncestor( + {required Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method( + #describeMissingAncestor, + [], + {#expectedAncestorType: expectedAncestorType}, + ), + returnValue: <_i4.DiagnosticsNode>[], + returnValueForMissingStub: <_i4.DiagnosticsNode>[], + ) as List<_i4.DiagnosticsNode>); + + @override + _i4.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod( + Invocation.method( + #describeOwnershipChain, + [name], + ), + returnValue: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), + ), + returnValueForMissingStub: _FakeDiagnosticsNode_2( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), + ), + ) as _i4.DiagnosticsNode); +} + /// A class which mocks [Camera]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCamera extends _i1.Mock implements _i9.Camera { +class MockCamera extends _i1.Mock implements _i12.Camera { @override - _i17.Future<_i2.CameraInfo> getCameraInfo() => (super.noSuchMethod( + _i17.Future<_i5.CameraInfo> getCameraInfo() => (super.noSuchMethod( Invocation.method( #getCameraInfo, [], ), - returnValue: _i17.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + returnValue: _i17.Future<_i5.CameraInfo>.value(_FakeCameraInfo_3( this, Invocation.method( #getCameraInfo, @@ -293,22 +476,22 @@ class MockCamera extends _i1.Mock implements _i9.Camera { ), )), returnValueForMissingStub: - _i17.Future<_i2.CameraInfo>.value(_FakeCameraInfo_0( + _i17.Future<_i5.CameraInfo>.value(_FakeCameraInfo_3( this, Invocation.method( #getCameraInfo, [], ), )), - ) as _i17.Future<_i2.CameraInfo>); + ) as _i17.Future<_i5.CameraInfo>); @override - _i17.Future<_i3.CameraControl> getCameraControl() => (super.noSuchMethod( + _i17.Future<_i6.CameraControl> getCameraControl() => (super.noSuchMethod( Invocation.method( #getCameraControl, [], ), - returnValue: _i17.Future<_i3.CameraControl>.value(_FakeCameraControl_1( + returnValue: _i17.Future<_i6.CameraControl>.value(_FakeCameraControl_4( this, Invocation.method( #getCameraControl, @@ -316,21 +499,21 @@ class MockCamera extends _i1.Mock implements _i9.Camera { ), )), returnValueForMissingStub: - _i17.Future<_i3.CameraControl>.value(_FakeCameraControl_1( + _i17.Future<_i6.CameraControl>.value(_FakeCameraControl_4( this, Invocation.method( #getCameraControl, [], ), )), - ) as _i17.Future<_i3.CameraControl>); + ) as _i17.Future<_i6.CameraControl>); } /// A class which mocks [CameraInfo]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { +class MockCameraInfo extends _i1.Mock implements _i5.CameraInfo { @override _i17.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( @@ -342,14 +525,14 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ) as _i17.Future); @override - _i17.Future<_i4.LiveData<_i20.CameraState>> getCameraState() => + _i17.Future<_i7.LiveData<_i20.CameraState>> getCameraState() => (super.noSuchMethod( Invocation.method( #getCameraState, [], ), - returnValue: _i17.Future<_i4.LiveData<_i20.CameraState>>.value( - _FakeLiveData_2<_i20.CameraState>( + returnValue: _i17.Future<_i7.LiveData<_i20.CameraState>>.value( + _FakeLiveData_5<_i20.CameraState>( this, Invocation.method( #getCameraState, @@ -357,23 +540,23 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i17.Future<_i4.LiveData<_i20.CameraState>>.value( - _FakeLiveData_2<_i20.CameraState>( + _i17.Future<_i7.LiveData<_i20.CameraState>>.value( + _FakeLiveData_5<_i20.CameraState>( this, Invocation.method( #getCameraState, [], ), )), - ) as _i17.Future<_i4.LiveData<_i20.CameraState>>); + ) as _i17.Future<_i7.LiveData<_i20.CameraState>>); @override - _i17.Future<_i5.ExposureState> getExposureState() => (super.noSuchMethod( + _i17.Future<_i8.ExposureState> getExposureState() => (super.noSuchMethod( Invocation.method( #getExposureState, [], ), - returnValue: _i17.Future<_i5.ExposureState>.value(_FakeExposureState_3( + returnValue: _i17.Future<_i8.ExposureState>.value(_FakeExposureState_6( this, Invocation.method( #getExposureState, @@ -381,24 +564,24 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i17.Future<_i5.ExposureState>.value(_FakeExposureState_3( + _i17.Future<_i8.ExposureState>.value(_FakeExposureState_6( this, Invocation.method( #getExposureState, [], ), )), - ) as _i17.Future<_i5.ExposureState>); + ) as _i17.Future<_i8.ExposureState>); @override - _i17.Future<_i4.LiveData<_i21.ZoomState>> getZoomState() => + _i17.Future<_i7.LiveData<_i21.ZoomState>> getZoomState() => (super.noSuchMethod( Invocation.method( #getZoomState, [], ), - returnValue: _i17.Future<_i4.LiveData<_i21.ZoomState>>.value( - _FakeLiveData_2<_i21.ZoomState>( + returnValue: _i17.Future<_i7.LiveData<_i21.ZoomState>>.value( + _FakeLiveData_5<_i21.ZoomState>( this, Invocation.method( #getZoomState, @@ -406,22 +589,22 @@ class MockCameraInfo extends _i1.Mock implements _i2.CameraInfo { ), )), returnValueForMissingStub: - _i17.Future<_i4.LiveData<_i21.ZoomState>>.value( - _FakeLiveData_2<_i21.ZoomState>( + _i17.Future<_i7.LiveData<_i21.ZoomState>>.value( + _FakeLiveData_5<_i21.ZoomState>( this, Invocation.method( #getZoomState, [], ), )), - ) as _i17.Future<_i4.LiveData<_i21.ZoomState>>); + ) as _i17.Future<_i7.LiveData<_i21.ZoomState>>); } /// A class which mocks [CameraControl]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraControl extends _i1.Mock implements _i3.CameraControl { +class MockCameraControl extends _i1.Mock implements _i6.CameraControl { @override _i17.Future enableTorch(bool? torch) => (super.noSuchMethod( Invocation.method( @@ -484,17 +667,17 @@ class MockCameraControl extends _i1.Mock implements _i3.CameraControl { class MockCamera2CameraControl extends _i1.Mock implements _i24.Camera2CameraControl { @override - _i3.CameraControl get cameraControl => (super.noSuchMethod( + _i6.CameraControl get cameraControl => (super.noSuchMethod( Invocation.getter(#cameraControl), - returnValue: _FakeCameraControl_1( + returnValue: _FakeCameraControl_4( this, Invocation.getter(#cameraControl), ), - returnValueForMissingStub: _FakeCameraControl_1( + returnValueForMissingStub: _FakeCameraControl_4( this, Invocation.getter(#cameraControl), ), - ) as _i3.CameraControl); + ) as _i6.CameraControl); @override _i17.Future addCaptureRequestOptions( @@ -509,23 +692,62 @@ class MockCamera2CameraControl extends _i1.Mock ) as _i17.Future); } +/// A class which mocks [Camera2CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCamera2CameraInfo extends _i1.Mock implements _i26.Camera2CameraInfo { + @override + _i17.Future getSupportedHardwareLevel() => (super.noSuchMethod( + Invocation.method( + #getSupportedHardwareLevel, + [], + ), + returnValue: _i17.Future.value(0), + returnValueForMissingStub: _i17.Future.value(0), + ) as _i17.Future); + + @override + _i17.Future getCameraId() => (super.noSuchMethod( + Invocation.method( + #getCameraId, + [], + ), + returnValue: _i17.Future.value(_i27.dummyValue( + this, + Invocation.method( + #getCameraId, + [], + ), + )), + returnValueForMissingStub: + _i17.Future.value(_i27.dummyValue( + this, + Invocation.method( + #getCameraId, + [], + ), + )), + ) as _i17.Future); +} + /// A class which mocks [CameraImageData]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData { +class MockCameraImageData extends _i1.Mock implements _i9.CameraImageData { @override - _i6.CameraImageFormat get format => (super.noSuchMethod( + _i9.CameraImageFormat get format => (super.noSuchMethod( Invocation.getter(#format), - returnValue: _FakeCameraImageFormat_4( + returnValue: _FakeCameraImageFormat_7( this, Invocation.getter(#format), ), - returnValueForMissingStub: _FakeCameraImageFormat_4( + returnValueForMissingStub: _FakeCameraImageFormat_7( this, Invocation.getter(#format), ), - ) as _i6.CameraImageFormat); + ) as _i9.CameraImageFormat); @override int get height => (super.noSuchMethod( @@ -542,50 +764,50 @@ class MockCameraImageData extends _i1.Mock implements _i6.CameraImageData { ) as int); @override - List<_i6.CameraImagePlane> get planes => (super.noSuchMethod( + List<_i9.CameraImagePlane> get planes => (super.noSuchMethod( Invocation.getter(#planes), - returnValue: <_i6.CameraImagePlane>[], - returnValueForMissingStub: <_i6.CameraImagePlane>[], - ) as List<_i6.CameraImagePlane>); + returnValue: <_i9.CameraImagePlane>[], + returnValueForMissingStub: <_i9.CameraImagePlane>[], + ) as List<_i9.CameraImagePlane>); } /// A class which mocks [CameraSelector]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockCameraSelector extends _i1.Mock implements _i26.CameraSelector { +class MockCameraSelector extends _i1.Mock implements _i28.CameraSelector { @override - _i17.Future> filter(List<_i2.CameraInfo>? cameraInfos) => + _i17.Future> filter(List<_i5.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), returnValue: - _i17.Future>.value(<_i2.CameraInfo>[]), + _i17.Future>.value(<_i5.CameraInfo>[]), returnValueForMissingStub: - _i17.Future>.value(<_i2.CameraInfo>[]), - ) as _i17.Future>); + _i17.Future>.value(<_i5.CameraInfo>[]), + ) as _i17.Future>); } /// A class which mocks [ExposureState]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockExposureState extends _i1.Mock implements _i5.ExposureState { +class MockExposureState extends _i1.Mock implements _i8.ExposureState { @override - _i7.ExposureCompensationRange get exposureCompensationRange => + _i10.ExposureCompensationRange get exposureCompensationRange => (super.noSuchMethod( Invocation.getter(#exposureCompensationRange), - returnValue: _FakeExposureCompensationRange_5( + returnValue: _FakeExposureCompensationRange_8( this, Invocation.getter(#exposureCompensationRange), ), - returnValueForMissingStub: _FakeExposureCompensationRange_5( + returnValueForMissingStub: _FakeExposureCompensationRange_8( this, Invocation.getter(#exposureCompensationRange), ), - ) as _i7.ExposureCompensationRange); + ) as _i10.ExposureCompensationRange); @override double get exposureCompensationStep => (super.noSuchMethod( @@ -599,21 +821,21 @@ class MockExposureState extends _i1.Mock implements _i5.ExposureState { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockFallbackStrategy extends _i1.Mock implements _i27.FallbackStrategy { +class MockFallbackStrategy extends _i1.Mock implements _i29.FallbackStrategy { @override - _i7.VideoQuality get quality => (super.noSuchMethod( + _i10.VideoQuality get quality => (super.noSuchMethod( Invocation.getter(#quality), - returnValue: _i7.VideoQuality.SD, - returnValueForMissingStub: _i7.VideoQuality.SD, - ) as _i7.VideoQuality); + returnValue: _i10.VideoQuality.SD, + returnValueForMissingStub: _i10.VideoQuality.SD, + ) as _i10.VideoQuality); @override - _i7.VideoResolutionFallbackRule get fallbackRule => (super.noSuchMethod( + _i10.VideoResolutionFallbackRule get fallbackRule => (super.noSuchMethod( Invocation.getter(#fallbackRule), - returnValue: _i7.VideoResolutionFallbackRule.higherQualityOrLowerThan, + returnValue: _i10.VideoResolutionFallbackRule.higherQualityOrLowerThan, returnValueForMissingStub: - _i7.VideoResolutionFallbackRule.higherQualityOrLowerThan, - ) as _i7.VideoResolutionFallbackRule); + _i10.VideoResolutionFallbackRule.higherQualityOrLowerThan, + ) as _i10.VideoResolutionFallbackRule); } /// A class which mocks [FocusMeteringResult]. @@ -637,7 +859,7 @@ class MockFocusMeteringResult extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageAnalysis extends _i1.Mock implements _i28.ImageAnalysis { +class MockImageAnalysis extends _i1.Mock implements _i30.ImageAnalysis { @override _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -673,7 +895,7 @@ class MockImageAnalysis extends _i1.Mock implements _i28.ImageAnalysis { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { +class MockImageCapture extends _i1.Mock implements _i31.ImageCapture { @override _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -700,7 +922,7 @@ class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { #takePicture, [], ), - returnValue: _i17.Future.value(_i30.dummyValue( + returnValue: _i17.Future.value(_i27.dummyValue( this, Invocation.method( #takePicture, @@ -708,7 +930,7 @@ class MockImageCapture extends _i1.Mock implements _i29.ImageCapture { ), )), returnValueForMissingStub: - _i17.Future.value(_i30.dummyValue( + _i17.Future.value(_i27.dummyValue( this, Invocation.method( #takePicture, @@ -745,16 +967,16 @@ class MockImageProxy extends _i1.Mock implements _i18.ImageProxy { ) as int); @override - _i17.Future> getPlanes() => (super.noSuchMethod( + _i17.Future> getPlanes() => (super.noSuchMethod( Invocation.method( #getPlanes, [], ), returnValue: - _i17.Future>.value(<_i31.PlaneProxy>[]), + _i17.Future>.value(<_i32.PlaneProxy>[]), returnValueForMissingStub: - _i17.Future>.value(<_i31.PlaneProxy>[]), - ) as _i17.Future>); + _i17.Future>.value(<_i32.PlaneProxy>[]), + ) as _i17.Future>); @override _i17.Future close() => (super.noSuchMethod( @@ -771,7 +993,7 @@ class MockImageProxy extends _i1.Mock implements _i18.ImageProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockObserver extends _i1.Mock implements _i32.Observer<_i20.CameraState> { +class MockObserver extends _i1.Mock implements _i33.Observer<_i20.CameraState> { @override void Function(Object) get onChanged => (super.noSuchMethod( Invocation.getter(#onChanged), @@ -793,14 +1015,14 @@ class MockObserver extends _i1.Mock implements _i32.Observer<_i20.CameraState> { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { +class MockPendingRecording extends _i1.Mock implements _i13.PendingRecording { @override - _i17.Future<_i8.Recording> start() => (super.noSuchMethod( + _i17.Future<_i11.Recording> start() => (super.noSuchMethod( Invocation.method( #start, [], ), - returnValue: _i17.Future<_i8.Recording>.value(_FakeRecording_6( + returnValue: _i17.Future<_i11.Recording>.value(_FakeRecording_9( this, Invocation.method( #start, @@ -808,27 +1030,27 @@ class MockPendingRecording extends _i1.Mock implements _i10.PendingRecording { ), )), returnValueForMissingStub: - _i17.Future<_i8.Recording>.value(_FakeRecording_6( + _i17.Future<_i11.Recording>.value(_FakeRecording_9( this, Invocation.method( #start, [], ), )), - ) as _i17.Future<_i8.Recording>); + ) as _i17.Future<_i11.Recording>); } /// A class which mocks [PlaneProxy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPlaneProxy extends _i1.Mock implements _i31.PlaneProxy { +class MockPlaneProxy extends _i1.Mock implements _i32.PlaneProxy { @override - _i33.Uint8List get buffer => (super.noSuchMethod( + _i34.Uint8List get buffer => (super.noSuchMethod( Invocation.getter(#buffer), - returnValue: _i33.Uint8List(0), - returnValueForMissingStub: _i33.Uint8List(0), - ) as _i33.Uint8List); + returnValue: _i34.Uint8List(0), + returnValueForMissingStub: _i34.Uint8List(0), + ) as _i34.Uint8List); @override int get pixelStride => (super.noSuchMethod( @@ -849,7 +1071,7 @@ class MockPlaneProxy extends _i1.Mock implements _i31.PlaneProxy { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockPreview extends _i1.Mock implements _i34.Preview { +class MockPreview extends _i1.Mock implements _i35.Preview { @override _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( Invocation.method( @@ -880,13 +1102,13 @@ class MockPreview extends _i1.Mock implements _i34.Preview { ); @override - _i17.Future<_i7.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( + _i17.Future<_i10.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [], ), returnValue: - _i17.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( + _i17.Future<_i10.ResolutionInfo>.value(_FakeResolutionInfo_10( this, Invocation.method( #getResolutionInfo, @@ -894,14 +1116,14 @@ class MockPreview extends _i1.Mock implements _i34.Preview { ), )), returnValueForMissingStub: - _i17.Future<_i7.ResolutionInfo>.value(_FakeResolutionInfo_7( + _i17.Future<_i10.ResolutionInfo>.value(_FakeResolutionInfo_10( this, Invocation.method( #getResolutionInfo, [], ), )), - ) as _i17.Future<_i7.ResolutionInfo>); + ) as _i17.Future<_i10.ResolutionInfo>); } /// A class which mocks [ProcessCameraProvider]. @@ -909,24 +1131,24 @@ class MockPreview extends _i1.Mock implements _i34.Preview { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockProcessCameraProvider extends _i1.Mock - implements _i35.ProcessCameraProvider { + implements _i36.ProcessCameraProvider { @override - _i17.Future> getAvailableCameraInfos() => + _i17.Future> getAvailableCameraInfos() => (super.noSuchMethod( Invocation.method( #getAvailableCameraInfos, [], ), returnValue: - _i17.Future>.value(<_i2.CameraInfo>[]), + _i17.Future>.value(<_i5.CameraInfo>[]), returnValueForMissingStub: - _i17.Future>.value(<_i2.CameraInfo>[]), - ) as _i17.Future>); + _i17.Future>.value(<_i5.CameraInfo>[]), + ) as _i17.Future>); @override - _i17.Future<_i9.Camera> bindToLifecycle( - _i26.CameraSelector? cameraSelector, - List<_i36.UseCase>? useCases, + _i17.Future<_i12.Camera> bindToLifecycle( + _i28.CameraSelector? cameraSelector, + List<_i37.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( @@ -936,7 +1158,7 @@ class MockProcessCameraProvider extends _i1.Mock useCases, ], ), - returnValue: _i17.Future<_i9.Camera>.value(_FakeCamera_8( + returnValue: _i17.Future<_i12.Camera>.value(_FakeCamera_11( this, Invocation.method( #bindToLifecycle, @@ -946,7 +1168,8 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - returnValueForMissingStub: _i17.Future<_i9.Camera>.value(_FakeCamera_8( + returnValueForMissingStub: + _i17.Future<_i12.Camera>.value(_FakeCamera_11( this, Invocation.method( #bindToLifecycle, @@ -956,10 +1179,10 @@ class MockProcessCameraProvider extends _i1.Mock ], ), )), - ) as _i17.Future<_i9.Camera>); + ) as _i17.Future<_i12.Camera>); @override - _i17.Future isBound(_i36.UseCase? useCase) => (super.noSuchMethod( + _i17.Future isBound(_i37.UseCase? useCase) => (super.noSuchMethod( Invocation.method( #isBound, [useCase], @@ -969,7 +1192,7 @@ class MockProcessCameraProvider extends _i1.Mock ) as _i17.Future); @override - void unbind(List<_i36.UseCase>? useCases) => super.noSuchMethod( + void unbind(List<_i37.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], @@ -991,29 +1214,29 @@ class MockProcessCameraProvider extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockQualitySelector extends _i1.Mock implements _i37.QualitySelector { +class MockQualitySelector extends _i1.Mock implements _i38.QualitySelector { @override - List<_i7.VideoQualityData> get qualityList => (super.noSuchMethod( + List<_i10.VideoQualityData> get qualityList => (super.noSuchMethod( Invocation.getter(#qualityList), - returnValue: <_i7.VideoQualityData>[], - returnValueForMissingStub: <_i7.VideoQualityData>[], - ) as List<_i7.VideoQualityData>); + returnValue: <_i10.VideoQualityData>[], + returnValueForMissingStub: <_i10.VideoQualityData>[], + ) as List<_i10.VideoQualityData>); } /// A class which mocks [Recorder]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockRecorder extends _i1.Mock implements _i12.Recorder { +class MockRecorder extends _i1.Mock implements _i15.Recorder { @override - _i17.Future<_i10.PendingRecording> prepareRecording(String? path) => + _i17.Future<_i13.PendingRecording> prepareRecording(String? path) => (super.noSuchMethod( Invocation.method( #prepareRecording, [path], ), returnValue: - _i17.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( + _i17.Future<_i13.PendingRecording>.value(_FakePendingRecording_12( this, Invocation.method( #prepareRecording, @@ -1021,33 +1244,33 @@ class MockRecorder extends _i1.Mock implements _i12.Recorder { ), )), returnValueForMissingStub: - _i17.Future<_i10.PendingRecording>.value(_FakePendingRecording_9( + _i17.Future<_i13.PendingRecording>.value(_FakePendingRecording_12( this, Invocation.method( #prepareRecording, [path], ), )), - ) as _i17.Future<_i10.PendingRecording>); + ) as _i17.Future<_i13.PendingRecording>); } /// A class which mocks [ResolutionFilter]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockResolutionFilter extends _i1.Mock implements _i38.ResolutionFilter { +class MockResolutionFilter extends _i1.Mock implements _i39.ResolutionFilter { @override - _i11.Size get preferredResolution => (super.noSuchMethod( + _i14.Size get preferredResolution => (super.noSuchMethod( Invocation.getter(#preferredResolution), - returnValue: _FakeSize_10( + returnValue: _FakeSize_13( this, Invocation.getter(#preferredResolution), ), - returnValueForMissingStub: _FakeSize_10( + returnValueForMissingStub: _FakeSize_13( this, Invocation.getter(#preferredResolution), ), - ) as _i11.Size); + ) as _i14.Size); } /// A class which mocks [ResolutionSelector]. @@ -1055,20 +1278,20 @@ class MockResolutionFilter extends _i1.Mock implements _i38.ResolutionFilter { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionSelector extends _i1.Mock - implements _i39.ResolutionSelector {} + implements _i40.ResolutionSelector {} /// A class which mocks [ResolutionStrategy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockResolutionStrategy extends _i1.Mock - implements _i40.ResolutionStrategy {} + implements _i41.ResolutionStrategy {} /// A class which mocks [Recording]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockRecording extends _i1.Mock implements _i8.Recording { +class MockRecording extends _i1.Mock implements _i11.Recording { @override _i17.Future close() => (super.noSuchMethod( Invocation.method( @@ -1110,229 +1333,6 @@ class MockRecording extends _i1.Mock implements _i8.Recording { ) as _i17.Future); } -/// A class which mocks [VideoCapture]. -/// -/// See the documentation for Mockito's code generation for more information. -// ignore: must_be_immutable -class MockVideoCapture extends _i1.Mock implements _i41.VideoCapture { - @override - _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( - Invocation.method( - #setTargetRotation, - [rotation], - ), - returnValue: _i17.Future.value(), - returnValueForMissingStub: _i17.Future.value(), - ) as _i17.Future); - - @override - _i17.Future<_i12.Recorder> getOutput() => (super.noSuchMethod( - Invocation.method( - #getOutput, - [], - ), - returnValue: _i17.Future<_i12.Recorder>.value(_FakeRecorder_11( - this, - Invocation.method( - #getOutput, - [], - ), - )), - returnValueForMissingStub: - _i17.Future<_i12.Recorder>.value(_FakeRecorder_11( - this, - Invocation.method( - #getOutput, - [], - ), - )), - ) as _i17.Future<_i12.Recorder>); -} - -/// A class which mocks [BuildContext]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockBuildContext extends _i1.Mock implements _i13.BuildContext { - @override - _i13.Widget get widget => (super.noSuchMethod( - Invocation.getter(#widget), - returnValue: _FakeWidget_12( - this, - Invocation.getter(#widget), - ), - returnValueForMissingStub: _FakeWidget_12( - this, - Invocation.getter(#widget), - ), - ) as _i13.Widget); - - @override - bool get mounted => (super.noSuchMethod( - Invocation.getter(#mounted), - returnValue: false, - returnValueForMissingStub: false, - ) as bool); - - @override - bool get debugDoingBuild => (super.noSuchMethod( - Invocation.getter(#debugDoingBuild), - returnValue: false, - returnValueForMissingStub: false, - ) as bool); - - @override - _i13.InheritedWidget dependOnInheritedElement( - _i13.InheritedElement? ancestor, { - Object? aspect, - }) => - (super.noSuchMethod( - Invocation.method( - #dependOnInheritedElement, - [ancestor], - {#aspect: aspect}, - ), - returnValue: _FakeInheritedWidget_13( - this, - Invocation.method( - #dependOnInheritedElement, - [ancestor], - {#aspect: aspect}, - ), - ), - returnValueForMissingStub: _FakeInheritedWidget_13( - this, - Invocation.method( - #dependOnInheritedElement, - [ancestor], - {#aspect: aspect}, - ), - ), - ) as _i13.InheritedWidget); - - @override - void visitAncestorElements(_i13.ConditionalElementVisitor? visitor) => - super.noSuchMethod( - Invocation.method( - #visitAncestorElements, - [visitor], - ), - returnValueForMissingStub: null, - ); - - @override - void visitChildElements(_i13.ElementVisitor? visitor) => super.noSuchMethod( - Invocation.method( - #visitChildElements, - [visitor], - ), - returnValueForMissingStub: null, - ); - - @override - void dispatchNotification(_i13.Notification? notification) => - super.noSuchMethod( - Invocation.method( - #dispatchNotification, - [notification], - ), - returnValueForMissingStub: null, - ); - - @override - _i15.DiagnosticsNode describeElement( - String? name, { - _i15.DiagnosticsTreeStyle? style = _i15.DiagnosticsTreeStyle.errorProperty, - }) => - (super.noSuchMethod( - Invocation.method( - #describeElement, - [name], - {#style: style}, - ), - returnValue: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeElement, - [name], - {#style: style}, - ), - ), - returnValueForMissingStub: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeElement, - [name], - {#style: style}, - ), - ), - ) as _i15.DiagnosticsNode); - - @override - _i15.DiagnosticsNode describeWidget( - String? name, { - _i15.DiagnosticsTreeStyle? style = _i15.DiagnosticsTreeStyle.errorProperty, - }) => - (super.noSuchMethod( - Invocation.method( - #describeWidget, - [name], - {#style: style}, - ), - returnValue: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeWidget, - [name], - {#style: style}, - ), - ), - returnValueForMissingStub: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeWidget, - [name], - {#style: style}, - ), - ), - ) as _i15.DiagnosticsNode); - - @override - List<_i15.DiagnosticsNode> describeMissingAncestor( - {required Type? expectedAncestorType}) => - (super.noSuchMethod( - Invocation.method( - #describeMissingAncestor, - [], - {#expectedAncestorType: expectedAncestorType}, - ), - returnValue: <_i15.DiagnosticsNode>[], - returnValueForMissingStub: <_i15.DiagnosticsNode>[], - ) as List<_i15.DiagnosticsNode>); - - @override - _i15.DiagnosticsNode describeOwnershipChain(String? name) => - (super.noSuchMethod( - Invocation.method( - #describeOwnershipChain, - [name], - ), - returnValue: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeOwnershipChain, - [name], - ), - ), - returnValueForMissingStub: _FakeDiagnosticsNode_14( - this, - Invocation.method( - #describeOwnershipChain, - [name], - ), - ), - ) as _i15.DiagnosticsNode); -} - /// A class which mocks [TestInstanceManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. @@ -1354,17 +1354,17 @@ class MockTestInstanceManagerHostApi extends _i1.Mock class MockTestSystemServicesHostApi extends _i1.Mock implements _i42.TestSystemServicesHostApi { @override - _i17.Future<_i7.CameraPermissionsErrorData?> requestCameraPermissions( + _i17.Future<_i10.CameraPermissionsErrorData?> requestCameraPermissions( bool? enableAudio) => (super.noSuchMethod( Invocation.method( #requestCameraPermissions, [enableAudio], ), - returnValue: _i17.Future<_i7.CameraPermissionsErrorData?>.value(), + returnValue: _i17.Future<_i10.CameraPermissionsErrorData?>.value(), returnValueForMissingStub: - _i17.Future<_i7.CameraPermissionsErrorData?>.value(), - ) as _i17.Future<_i7.CameraPermissionsErrorData?>); + _i17.Future<_i10.CameraPermissionsErrorData?>.value(), + ) as _i17.Future<_i10.CameraPermissionsErrorData?>); @override String getTempFilePath( @@ -1379,7 +1379,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock suffix, ], ), - returnValue: _i30.dummyValue( + returnValue: _i27.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1389,7 +1389,7 @@ class MockTestSystemServicesHostApi extends _i1.Mock ], ), ), - returnValueForMissingStub: _i30.dummyValue( + returnValueForMissingStub: _i27.dummyValue( this, Invocation.method( #getTempFilePath, @@ -1402,6 +1402,45 @@ class MockTestSystemServicesHostApi extends _i1.Mock ) as String); } +/// A class which mocks [VideoCapture]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockVideoCapture extends _i1.Mock implements _i43.VideoCapture { + @override + _i17.Future setTargetRotation(int? rotation) => (super.noSuchMethod( + Invocation.method( + #setTargetRotation, + [rotation], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + + @override + _i17.Future<_i15.Recorder> getOutput() => (super.noSuchMethod( + Invocation.method( + #getOutput, + [], + ), + returnValue: _i17.Future<_i15.Recorder>.value(_FakeRecorder_14( + this, + Invocation.method( + #getOutput, + [], + ), + )), + returnValueForMissingStub: + _i17.Future<_i15.Recorder>.value(_FakeRecorder_14( + this, + Invocation.method( + #getOutput, + [], + ), + )), + ) as _i17.Future<_i15.Recorder>); +} + /// A class which mocks [ZoomState]. /// /// See the documentation for Mockito's code generation for more information. @@ -1427,13 +1466,13 @@ class MockZoomState extends _i1.Mock implements _i21.ZoomState { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockLiveCameraState extends _i1.Mock - implements _i4.LiveData<_i20.CameraState> { + implements _i7.LiveData<_i20.CameraState> { MockLiveCameraState() { _i1.throwOnMissingStub(this); } @override - _i17.Future observe(_i32.Observer<_i20.CameraState>? observer) => + _i17.Future observe(_i33.Observer<_i20.CameraState>? observer) => (super.noSuchMethod( Invocation.method( #observe, @@ -1459,13 +1498,13 @@ class MockLiveCameraState extends _i1.Mock /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockLiveZoomState extends _i1.Mock - implements _i4.LiveData<_i21.ZoomState> { + implements _i7.LiveData<_i21.ZoomState> { MockLiveZoomState() { _i1.throwOnMissingStub(this); } @override - _i17.Future observe(_i32.Observer<_i21.ZoomState>? observer) => + _i17.Future observe(_i33.Observer<_i21.ZoomState>? observer) => (super.noSuchMethod( Invocation.method( #observe, diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart new file mode 100644 index 00000000000..0766eee37e8 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart @@ -0,0 +1,155 @@ +// 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:camera_android_camerax/src/camera2_camera_info.dart'; +import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_metadata.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'camera2_camera_info_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([ + TestCamera2CameraInfoHostApi, + TestInstanceManagerHostApi, + CameraInfo +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + + group('Camera2CameraInfo', () { + tearDown(() => TestCamera2CameraInfoHostApi.setup(null)); + + test('from returns expected Camera2CameraInfo instance', () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + final CameraInfo mockCameraInfo = MockCameraInfo(); + const int camera2CameraInfoId = 33; + const int mockCameraInfoId = 44; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + instanceManager.addHostCreatedInstance( + mockCameraInfo, + mockCameraInfoId, + onCopy: (_) => CameraInfo.detached(), + ); + + when(mockApi.createFrom(mockCameraInfoId)) + .thenAnswer((_) => camera2CameraInfoId); + expect( + await Camera2CameraInfo.from(mockCameraInfo, + instanceManager: instanceManager), + equals(camera2CameraInfo)); + verify(mockApi.createFrom(mockCameraInfoId)); + }); + + test('detached constructor does not create Camera2CameraInfo on Java side', + () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + + verifyNever(mockApi.createFrom(argThat(isA()))); + }); + + test( + 'getSupportedHardwareLevel makes call to retrieve supported hardware level', + () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + const int camera2CameraInfoId = 9; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + + const int expectedSupportedHardwareLevel = + CameraMetadata.infoSupportedHardwareLevelExternal; + when(mockApi.getSupportedHardwareLevel(camera2CameraInfoId)) + .thenReturn(expectedSupportedHardwareLevel); + expect(await camera2CameraInfo.getSupportedHardwareLevel(), + equals(expectedSupportedHardwareLevel)); + + verify(mockApi.getSupportedHardwareLevel(camera2CameraInfoId)); + }); + + test('getCameraId makes call to retrieve camera ID', () async { + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + const int camera2CameraInfoId = 19; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + + const String expectedCameraId = 'testCameraId'; + when(mockApi.getCameraId(camera2CameraInfoId)) + .thenReturn(expectedCameraId); + expect(await camera2CameraInfo.getCameraId(), equals(expectedCameraId)); + + verify(mockApi.getCameraId(camera2CameraInfoId)); + }); + + test('flutterApi create makes call to create expected instance type', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfoFlutterApi flutterApi = + Camera2CameraInfoFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.create(0); + + expect(instanceManager.getInstanceWithWeakReference(0), + isA()); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart new file mode 100644 index 00000000000..9060c51874a --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart @@ -0,0 +1,179 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in camera_android_camerax/test/camera2_camera_info_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i7; + +import 'package:camera_android_camerax/src/camera_info.dart' as _i6; +import 'package:camera_android_camerax/src/camera_state.dart' as _i8; +import 'package:camera_android_camerax/src/exposure_state.dart' as _i3; +import 'package:camera_android_camerax/src/live_data.dart' as _i2; +import 'package:camera_android_camerax/src/zoom_state.dart' as _i9; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +import 'test_camerax_library.g.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeLiveData_0 extends _i1.SmartFake + implements _i2.LiveData { + _FakeLiveData_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeExposureState_1 extends _i1.SmartFake implements _i3.ExposureState { + _FakeExposureState_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [TestCamera2CameraInfoHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestCamera2CameraInfoHostApi extends _i1.Mock + implements _i4.TestCamera2CameraInfoHostApi { + MockTestCamera2CameraInfoHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + int createFrom(int? cameraInfoIdentifier) => (super.noSuchMethod( + Invocation.method( + #createFrom, + [cameraInfoIdentifier], + ), + returnValue: 0, + ) as int); + + @override + int getSupportedHardwareLevel(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getSupportedHardwareLevel, + [identifier], + ), + returnValue: 0, + ) as int); + + @override + String getCameraId(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getCameraId, + [identifier], + ), + returnValue: _i5.dummyValue( + this, + Invocation.method( + #getCameraId, + [identifier], + ), + ), + ) as String); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i4.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void clear() => super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCameraInfo extends _i1.Mock implements _i6.CameraInfo { + MockCameraInfo() { + _i1.throwOnMissingStub(this); + } + + @override + _i7.Future getSensorRotationDegrees() => (super.noSuchMethod( + Invocation.method( + #getSensorRotationDegrees, + [], + ), + returnValue: _i7.Future.value(0), + ) as _i7.Future); + + @override + _i7.Future<_i2.LiveData<_i8.CameraState>> getCameraState() => + (super.noSuchMethod( + Invocation.method( + #getCameraState, + [], + ), + returnValue: _i7.Future<_i2.LiveData<_i8.CameraState>>.value( + _FakeLiveData_0<_i8.CameraState>( + this, + Invocation.method( + #getCameraState, + [], + ), + )), + ) as _i7.Future<_i2.LiveData<_i8.CameraState>>); + + @override + _i7.Future<_i3.ExposureState> getExposureState() => (super.noSuchMethod( + Invocation.method( + #getExposureState, + [], + ), + returnValue: _i7.Future<_i3.ExposureState>.value(_FakeExposureState_1( + this, + Invocation.method( + #getExposureState, + [], + ), + )), + ) as _i7.Future<_i3.ExposureState>); + + @override + _i7.Future<_i2.LiveData<_i9.ZoomState>> getZoomState() => (super.noSuchMethod( + Invocation.method( + #getZoomState, + [], + ), + returnValue: _i7.Future<_i2.LiveData<_i9.ZoomState>>.value( + _FakeLiveData_0<_i9.ZoomState>( + this, + Invocation.method( + #getZoomState, + [], + ), + )), + ) as _i7.Future<_i2.LiveData<_i9.ZoomState>>); +} diff --git a/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart b/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart index 145ceeb278a..de24bb0b3f4 100644 --- a/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart +++ b/packages/camera/camera_android_camerax/test/device_orientation_manager_test.dart @@ -52,7 +52,7 @@ void main() { final MockTestDeviceOrientationManagerHostApi mockApi = MockTestDeviceOrientationManagerHostApi(); TestDeviceOrientationManagerHostApi.setup(mockApi); - const int expectedRotation = Surface.ROTATION_180; + const int expectedRotation = Surface.rotation180; when(mockApi.getDefaultDisplayRotation()).thenReturn(expectedRotation); diff --git a/packages/camera/camera_android_camerax/test/image_analysis_test.dart b/packages/camera/camera_android_camerax/test/image_analysis_test.dart index 1db792cf6d3..df220357508 100644 --- a/packages/camera/camera_android_camerax/test/image_analysis_test.dart +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.dart @@ -41,7 +41,7 @@ void main() { ); ImageAnalysis.detached( - initialTargetRotation: Surface.ROTATION_270, + initialTargetRotation: Surface.rotation270, resolutionSelector: MockResolutionSelector(), instanceManager: instanceManager, ); @@ -58,7 +58,7 @@ void main() { onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_90; + const int targetRotation = Surface.rotation90; final MockResolutionSelector mockResolutionSelector = MockResolutionSelector(); const int mockResolutionSelectorId = 24; @@ -91,7 +91,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final ImageAnalysis imageAnalysis = ImageAnalysis.detached( instanceManager: instanceManager, ); diff --git a/packages/camera/camera_android_camerax/test/image_capture_test.dart b/packages/camera/camera_android_camerax/test/image_capture_test.dart index e9e9f0c1855..56cbd8fcc18 100644 --- a/packages/camera/camera_android_camerax/test/image_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/image_capture_test.dart @@ -36,7 +36,7 @@ void main() { ); ImageCapture.detached( instanceManager: instanceManager, - initialTargetRotation: Surface.ROTATION_180, + initialTargetRotation: Surface.rotation180, targetFlashMode: ImageCapture.flashModeOn, resolutionSelector: MockResolutionSelector(), ); @@ -53,7 +53,7 @@ void main() { onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_270; + const int targetRotation = Surface.rotation270; const int targetFlashMode = ImageCapture.flashModeAuto; final MockResolutionSelector mockResolutionSelector = MockResolutionSelector(); @@ -112,7 +112,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final ImageCapture imageCapture = ImageCapture.detached( instanceManager: instanceManager, ); diff --git a/packages/camera/camera_android_camerax/test/preview_test.dart b/packages/camera/camera_android_camerax/test/preview_test.dart index cda1252d538..b3cbc257b0c 100644 --- a/packages/camera/camera_android_camerax/test/preview_test.dart +++ b/packages/camera/camera_android_camerax/test/preview_test.dart @@ -34,7 +34,7 @@ void main() { ); Preview.detached( instanceManager: instanceManager, - initialTargetRotation: Surface.ROTATION_90, + initialTargetRotation: Surface.rotation90, resolutionSelector: MockResolutionSelector(), ); @@ -49,7 +49,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_90; + const int targetRotation = Surface.rotation90; final MockResolutionSelector mockResolutionSelector = MockResolutionSelector(); const int mockResolutionSelectorId = 24; @@ -81,7 +81,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final Preview preview = Preview.detached( instanceManager: instanceManager, ); 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 8080e4276fc..105447e5268 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 @@ -2230,6 +2230,9 @@ class _TestCaptureRequestOptionsHostApiCodec extends StandardMessageCodec { } else if (value is VideoQualityData) { buffer.putUint8(134); writeValue(buffer, value.encode()); + } else if (value is VideoRecordEventData) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -2252,6 +2255,8 @@ class _TestCaptureRequestOptionsHostApiCodec extends StandardMessageCodec { return ResolutionInfo.decode(readValue(buffer)!); case 134: return VideoQualityData.decode(readValue(buffer)!); + case 135: + return VideoRecordEventData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -2428,3 +2433,86 @@ abstract class TestResolutionFilterHostApi { } } } + +abstract class TestCamera2CameraInfoHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + int createFrom(int cameraInfoIdentifier); + + int getSupportedHardwareLevel(int identifier); + + String getCameraId(int identifier); + + static void setup(TestCamera2CameraInfoHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom', 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.Camera2CameraInfoHostApi.createFrom was null.'); + final List args = (message as List?)!; + final int? arg_cameraInfoIdentifier = (args[0] as int?); + assert(arg_cameraInfoIdentifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.createFrom was null, expected non-null int.'); + final int output = api.createFrom(arg_cameraInfoIdentifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getSupportedHardwareLevel', + 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.Camera2CameraInfoHostApi.getSupportedHardwareLevel 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.Camera2CameraInfoHostApi.getSupportedHardwareLevel was null, expected non-null int.'); + final int output = api.getSupportedHardwareLevel(arg_identifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getCameraId', 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.Camera2CameraInfoHostApi.getCameraId 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.Camera2CameraInfoHostApi.getCameraId was null, expected non-null int.'); + final String output = api.getCameraId(arg_identifier!); + return [output]; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/test/video_capture_test.dart b/packages/camera/camera_android_camerax/test/video_capture_test.dart index 6954b352ade..c5fd1f078b9 100644 --- a/packages/camera/camera_android_camerax/test/video_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/video_capture_test.dart @@ -60,7 +60,7 @@ void main() { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); - const int targetRotation = Surface.ROTATION_180; + const int targetRotation = Surface.rotation180; final VideoCapture videoCapture = VideoCapture.detached( instanceManager: instanceManager, ); diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 584c95f6adb..5012f7d965b 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.16 + +* Converts Dart-to-host communcation to Pigeon. +* Fixes a race condition in camera disposal. + ## 0.9.15+4 * Converts host-to-Dart communcation to Pigeon. @@ -121,11 +126,11 @@ ## 0.9.8+5 -* Fixes a regression introduced in 0.9.8+4 where the stream handler is not set. +* Fixes a regression introduced in 0.9.8+4 where the stream handler is not set. ## 0.9.8+4 -* Fixes a crash due to sending orientation change events when the engine is torn down. +* Fixes a crash due to sending orientation change events when the engine is torn down. ## 0.9.8+3 diff --git a/packages/camera/camera_avfoundation/README.md b/packages/camera/camera_avfoundation/README.md index 970450c59da..459211b046c 100644 --- a/packages/camera/camera_avfoundation/README.md +++ b/packages/camera/camera_avfoundation/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/camera -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index dc00e49c042..3ca0527f601 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -29,7 +29,6 @@ E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; E0B0D2BB27DFF2AF00E71E4B /* CameraPermissionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */; }; - E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; }; E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; }; E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */; }; @@ -95,7 +94,6 @@ E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPermissionTests.m; sourceTree = ""; }; - E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = ""; }; E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = ""; }; E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = ""; }; @@ -132,7 +130,6 @@ 03BB766C2665316900CE5A93 /* Info.plist */, 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, - E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */, E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, @@ -449,7 +446,6 @@ E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, - E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */, E0B0D2BB27DFF2AF00E71E4B /* CameraPermissionTests.m in Sources */, E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */, ); @@ -677,7 +673,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.bradenbagby.test; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -701,7 +697,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.bradenbagby.test; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m index bc3713b7478..226d6bfb1a5 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m @@ -18,22 +18,25 @@ - (void)testFixForCaptureSessionQueueNullPointerCrashDueToRaceCondition { [self expectationWithDescription:@"dispose's result block must be called"]; XCTestExpectation *createExpectation = [self expectationWithDescription:@"create's result block must be called"]; - FlutterMethodCall *disposeCall = [FlutterMethodCall methodCallWithMethodName:@"dispose" - arguments:nil]; - FlutterMethodCall *createCall = [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}]; // Mimic a dispose call followed by a create call, which can be triggered by slightly dragging the // home bar, causing the app to be inactive, and immediately regain active. - [camera handleMethodCall:disposeCall - result:^(id _Nullable result) { - [disposeExpectation fulfill]; - }]; - [camera createCameraOnSessionQueueWithCreateMethodCall:createCall - result:^(id _Nullable result) { - [createExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; + [camera disposeCamera:0 + completion:^(FlutterError *_Nullable error) { + [disposeExpectation fulfill]; + }]; + [camera createCameraOnSessionQueueWithName:@"acamera" + settings:[FCPPlatformMediaSettings + makeWithResolutionPreset: + FCPPlatformResolutionPresetMedium + framesPerSecond:nil + videoBitrate:nil + audioBitrate:nil + enableAudio:YES] + completion:^(NSNumber *_Nullable result, + FlutterError *_Nullable error) { + [createExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; // `captureSessionQueue` must not be nil after `create` call. Otherwise a nil // `captureSessionQueue` passed into `AVCaptureVideoDataOutput::setSampleBufferDelegate:queue:` // API will cause a crash. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.m index 577304018de..d13f5a77ced 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.m @@ -114,11 +114,9 @@ - (void)testSetFocusPointWithResult_SetsFocusPointOfInterest { [_camera setValue:_mockDevice forKey:@"captureDevice"]; // Run test - [_camera - setFocusPointWithResult:^(id _Nullable result) { - } - x:1 - y:1]; + [_camera setFocusPoint:[FCPPlatformPoint makeWithX:1 y:1] + withCompletion:^(FlutterError *_Nullable error){ + }]; // Verify the focus point of interest has been set OCMVerify([_mockDevice setFocusPointOfInterest:CGPointMake(1, 1)]); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m index 423b8e88989..55fc44e10cb 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m @@ -28,22 +28,24 @@ - (void)testCreate_ShouldCallResultOnMainThread { OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); // Set up method call - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}]; - - __block id resultValue; - [camera createCameraOnSessionQueueWithCreateMethodCall:call - result:^(id _Nullable result) { - resultValue = result; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; + __block NSNumber *resultValue; + [camera createCameraOnSessionQueueWithName:@"acamera" + settings:[FCPPlatformMediaSettings + makeWithResolutionPreset: + FCPPlatformResolutionPresetMedium + framesPerSecond:nil + videoBitrate:nil + audioBitrate:nil + enableAudio:YES] + completion:^(NSNumber *_Nullable result, + FlutterError *_Nullable error) { + resultValue = result; + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; // Verify the result - NSDictionary *dictionaryResult = (NSDictionary *)resultValue; - XCTAssertNotNil(dictionaryResult); - XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]); + XCTAssertNotNil(resultValue); } @end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.m index 2ce7b8676d3..96ae19ff14d 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.m @@ -16,16 +16,14 @@ @implementation CameraPreviewPauseTests - (void)testPausePreviewWithResult_shouldPausePreview { FLTCam *camera = [[FLTCam alloc] init]; - [camera pausePreviewWithResult:^(id _Nullable result){ - }]; + [camera pausePreview]; XCTAssertTrue(camera.isPreviewPaused); } - (void)testResumePreviewWithResult_shouldResumePreview { FLTCam *camera = [[FLTCam alloc] init]; - [camera resumePreviewWithResult:^(id _Nullable result){ - }]; + [camera resumePreview]; XCTAssertFalse(camera.isPreviewPaused); } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m index 14ced24bfc1..5b865d464dc 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m @@ -15,68 +15,39 @@ @implementation CameraPropertiesTests #pragma mark - flash mode tests -- (void)testFLTGetFLTFlashModeForString { - XCTAssertEqual(FLTFlashModeOff, FLTGetFLTFlashModeForString(@"off")); - XCTAssertEqual(FLTFlashModeAuto, FLTGetFLTFlashModeForString(@"auto")); - XCTAssertEqual(FLTFlashModeAlways, FLTGetFLTFlashModeForString(@"always")); - XCTAssertEqual(FLTFlashModeTorch, FLTGetFLTFlashModeForString(@"torch")); - XCTAssertEqual(FLTFlashModeInvalid, FLTGetFLTFlashModeForString(@"unknown")); -} - -- (void)testFLTGetAVCaptureFlashModeForFLTFlashMode { - XCTAssertEqual(AVCaptureFlashModeOff, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeOff)); - XCTAssertEqual(AVCaptureFlashModeAuto, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeAuto)); - XCTAssertEqual(AVCaptureFlashModeOn, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeAlways)); - XCTAssertEqual(-1, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeTorch)); -} - -#pragma mark - exposure mode tests - -- (void)testFCPGetExposureModeForString { - XCTAssertEqual(FCPPlatformExposureModeAuto, FCPGetExposureModeForString(@"auto")); - XCTAssertEqual(FCPPlatformExposureModeLocked, FCPGetExposureModeForString(@"locked")); -} - -#pragma mark - focus mode tests - -- (void)testFLTGetFLTFocusModeForString { - XCTAssertEqual(FCPPlatformFocusModeAuto, FCPGetFocusModeForString(@"auto")); - XCTAssertEqual(FCPPlatformFocusModeLocked, FCPGetFocusModeForString(@"locked")); -} - -#pragma mark - resolution preset tests - -- (void)testFLTGetFLTResolutionPresetForString { - XCTAssertEqual(FLTResolutionPresetVeryLow, FLTGetFLTResolutionPresetForString(@"veryLow")); - XCTAssertEqual(FLTResolutionPresetLow, FLTGetFLTResolutionPresetForString(@"low")); - XCTAssertEqual(FLTResolutionPresetMedium, FLTGetFLTResolutionPresetForString(@"medium")); - XCTAssertEqual(FLTResolutionPresetHigh, FLTGetFLTResolutionPresetForString(@"high")); - XCTAssertEqual(FLTResolutionPresetVeryHigh, FLTGetFLTResolutionPresetForString(@"veryHigh")); - XCTAssertEqual(FLTResolutionPresetUltraHigh, FLTGetFLTResolutionPresetForString(@"ultraHigh")); - XCTAssertEqual(FLTResolutionPresetMax, FLTGetFLTResolutionPresetForString(@"max")); - XCTAssertEqual(FLTResolutionPresetInvalid, FLTGetFLTResolutionPresetForString(@"unknown")); +- (void)testFCPGetAVCaptureFlashModeForPigeonFlashMode { + XCTAssertEqual(AVCaptureFlashModeOff, + FCPGetAVCaptureFlashModeForPigeonFlashMode(FCPPlatformFlashModeOff)); + XCTAssertEqual(AVCaptureFlashModeAuto, + FCPGetAVCaptureFlashModeForPigeonFlashMode(FCPPlatformFlashModeAuto)); + XCTAssertEqual(AVCaptureFlashModeOn, + FCPGetAVCaptureFlashModeForPigeonFlashMode(FCPPlatformFlashModeAlways)); + XCTAssertThrows(FCPGetAVCaptureFlashModeForPigeonFlashMode(FCPPlatformFlashModeTorch)); } #pragma mark - video format tests -- (void)testFLTGetVideoFormatFromString { - XCTAssertEqual(kCVPixelFormatType_32BGRA, FLTGetVideoFormatFromString(@"bgra8888")); +- (void)testFCPGetPixelFormatForPigeonFormat { + XCTAssertEqual(kCVPixelFormatType_32BGRA, + FCPGetPixelFormatForPigeonFormat(FCPPlatformImageFormatGroupBgra8888)); XCTAssertEqual(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, - FLTGetVideoFormatFromString(@"yuv420")); - XCTAssertEqual(kCVPixelFormatType_32BGRA, FLTGetVideoFormatFromString(@"unknown")); + FCPGetPixelFormatForPigeonFormat(FCPPlatformImageFormatGroupYuv420)); } #pragma mark - device orientation tests -- (void)testFLTGetUIDeviceOrientationForString { +- (void)testFCPGetUIDeviceOrientationForPigeonDeviceOrientation { XCTAssertEqual(UIDeviceOrientationPortraitUpsideDown, - FLTGetUIDeviceOrientationForString(@"portraitDown")); + FCPGetUIDeviceOrientationForPigeonDeviceOrientation( + FCPPlatformDeviceOrientationPortraitDown)); XCTAssertEqual(UIDeviceOrientationLandscapeLeft, - FLTGetUIDeviceOrientationForString(@"landscapeLeft")); + FCPGetUIDeviceOrientationForPigeonDeviceOrientation( + FCPPlatformDeviceOrientationLandscapeLeft)); XCTAssertEqual(UIDeviceOrientationLandscapeRight, - FLTGetUIDeviceOrientationForString(@"landscapeRight")); - XCTAssertEqual(UIDeviceOrientationPortrait, FLTGetUIDeviceOrientationForString(@"portraitUp")); - XCTAssertEqual(UIDeviceOrientationUnknown, FLTGetUIDeviceOrientationForString(@"unknown")); + FCPGetUIDeviceOrientationForPigeonDeviceOrientation( + FCPPlatformDeviceOrientationLandscapeRight)); + XCTAssertEqual(UIDeviceOrientationPortrait, FCPGetUIDeviceOrientationForPigeonDeviceOrientation( + FCPPlatformDeviceOrientationPortraitUp)); } - (void)testFLTGetStringForUIDeviceOrientation { @@ -93,12 +64,4 @@ - (void)testFLTGetStringForUIDeviceOrientation { FCPGetPigeonDeviceOrientationForOrientation(-1)); } -#pragma mark - file format tests - -- (void)testFLTGetFileFormatForString { - XCTAssertEqual(FCPFileFormatJPEG, FCPGetFileFormatFromString(@"jpg")); - XCTAssertEqual(FCPFileFormatHEIF, FCPGetFileFormatFromString(@"heif")); - XCTAssertEqual(FCPFileFormatInvalid, FCPGetFileFormatFromString(@"unknown")); -} - @end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m index a5130ad8288..28f8d5de4e9 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.m @@ -30,7 +30,8 @@ - (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { OCMExpect([captureDeviceMock lockForConfiguration:NULL]).andReturn(YES); OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); - FLTCreateCamWithVideoDimensionsForFormat(videoSessionMock, @"max", captureDeviceMock, + FLTCreateCamWithVideoDimensionsForFormat(videoSessionMock, FCPPlatformResolutionPresetMax, + captureDeviceMock, ^CMVideoDimensions(AVCaptureDeviceFormat *format) { CMVideoDimensions videoDimensions; videoDimensions.width = 1; @@ -53,7 +54,7 @@ - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionP OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); - FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); + FLTCreateCamWithVideoCaptureSession(videoSessionMock, FCPPlatformResolutionPresetMax); OCMVerifyAll(videoSessionMock); } @@ -70,7 +71,7 @@ - (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSe // Expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession. OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); - FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); + FLTCreateCamWithVideoCaptureSession(videoSessionMock, FCPPlatformResolutionPresetUltraHigh); OCMVerifyAll(videoSessionMock); } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m index 3177fe460ea..1962a6b7457 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m @@ -9,134 +9,11 @@ #import #import "CameraTestUtils.h" -static const char *gTestResolutionPreset = "medium"; +static const FCPPlatformResolutionPreset gTestResolutionPreset = FCPPlatformResolutionPresetMedium; static const int gTestFramesPerSecond = 15; static const int gTestVideoBitrate = 200000; static const int gTestAudioBitrate = 32000; -static const bool gTestEnableAudio = YES; - -@interface CameraCreateWithMediaSettingsParseTests : XCTestCase -@end - -/// Expect that optional positive numbers can be parsed -@implementation CameraCreateWithMediaSettingsParseTests - -- (FlutterError *)failingTestWithArguments:(NSDictionary *)arguments { - CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; - - // Set up method call - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"create" - arguments:arguments]; - - __block id resultValue; - [camera createCameraOnSessionQueueWithCreateMethodCall:call - result:^(id _Nullable result) { - resultValue = result; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - - // Verify the result - XCTAssertNotNil(resultValue); - XCTAssertTrue([resultValue isKindOfClass:[FlutterError class]]); - return (FlutterError *)resultValue; -} - -- (void)goodTestWithArguments:(NSDictionary *)arguments { - CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; - - // Set up mocks for initWithCameraName method - id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); - OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]]) - .andReturn([AVCaptureInput alloc]); - - id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock); - OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - - // Set up method call - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"create" - arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}]; - - __block id resultValue; - [camera createCameraOnSessionQueueWithCreateMethodCall:call - result:^(id _Nullable result) { - resultValue = result; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - - // Verify the result - XCTAssertNotNil(resultValue); - XCTAssertFalse([resultValue isKindOfClass:[FlutterError class]]); - NSDictionary *dictionaryResult = (NSDictionary *)resultValue; - XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]); -} - -- (void)testCameraCreateWithMediaSettings_shouldRejectNegativeIntNumbers { - FlutterError *error = - [self failingTestWithArguments:@{@"fps" : @(-1), @"resolutionPreset" : @"medium"}]; - XCTAssertEqualObjects(error.message, @"fps should be a positive number", - "should reject negative int number"); -} - -- (void)testCameraCreateWithMediaSettings_shouldRejectNegativeFloatingPointNumbers { - FlutterError *error = - [self failingTestWithArguments:@{@"fps" : @(-3.7), @"resolutionPreset" : @"medium"}]; - XCTAssertEqualObjects(error.message, @"fps should be a positive number", - "should reject negative floating point number"); -} - -- (void)testCameraCreateWithMediaSettings_nanShouldBeParsedAsNil { - FlutterError *error = - [self failingTestWithArguments:@{@"fps" : @(NAN), @"resolutionPreset" : @"medium"}]; - XCTAssertEqualObjects(error.message, @"fps should not be a nan", "should reject NAN"); -} - -- (void)testCameraCreateWithMediaSettings_shouldNotRejectNilArguments { - [self goodTestWithArguments:@{@"resolutionPreset" : @"medium"}]; -} - -- (void)testCameraCreateWithMediaSettings_shouldAcceptNull { - [self goodTestWithArguments:@{@"fps" : [NSNull null], @"resolutionPreset" : @"medium"}]; -} - -- (void)testCameraCreateWithMediaSettings_shouldAcceptPositiveDecimalNumbers { - [self goodTestWithArguments:@{@"fps" : @(5), @"resolutionPreset" : @"medium"}]; -} - -- (void)testCameraCreateWithMediaSettings_shouldAcceptPositiveFloatingPointNumbers { - [self goodTestWithArguments:@{@"fps" : @(3.7), @"resolutionPreset" : @"medium"}]; -} - -- (void)testCameraCreateWithMediaSettings_shouldRejectWrongVideoBitrate { - FlutterError *error = - [self failingTestWithArguments:@{@"videoBitrate" : @(-1), @"resolutionPreset" : @"medium"}]; - XCTAssertEqualObjects(error.message, @"videoBitrate should be a positive number", - "should reject wrong video bitrate"); -} - -- (void)testCameraCreateWithMediaSettings_shouldRejectWrongAudioBitrate { - FlutterError *error = - [self failingTestWithArguments:@{@"audioBitrate" : @(-1), @"resolutionPreset" : @"medium"}]; - XCTAssertEqualObjects(error.message, @"audioBitrate should be a positive number", - "should reject wrong audio bitrate"); -} - -- (void)testCameraCreateWithMediaSettings_shouldAcceptGoodVideoBitrate { - [self goodTestWithArguments:@{@"videoBitrate" : @(200000), @"resolutionPreset" : @"medium"}]; -} - -- (void)testCameraCreateWithMediaSettings_shouldAcceptGoodAudioBitrate { - [self goodTestWithArguments:@{@"audioBitrate" : @(32000), @"resolutionPreset" : @"medium"}]; -} - -@end +static const BOOL gTestEnableAudio = YES; @interface CameraSettingsTests : XCTestCase @end @@ -255,11 +132,12 @@ @implementation CameraSettingsTests /// Expect that FPS, video and audio bitrate are passed to camera device and asset writer. - (void)testSettings_shouldPassConfigurationToCameraDeviceAndWriter { - FLTCamMediaSettings *settings = - [[FLTCamMediaSettings alloc] initWithFramesPerSecond:@(gTestFramesPerSecond) - videoBitrate:@(gTestVideoBitrate) - audioBitrate:@(gTestAudioBitrate) - enableAudio:gTestEnableAudio]; + FCPPlatformMediaSettings *settings = + [FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset + framesPerSecond:@(gTestFramesPerSecond) + videoBitrate:@(gTestVideoBitrate) + audioBitrate:@(gTestAudioBitrate) + enableAudio:gTestEnableAudio]; TestMediaSettingsAVWrapper *injectedWrapper = [[TestMediaSettingsAVWrapper alloc] initWithTestCase:self]; @@ -275,9 +153,10 @@ - (void)testSettings_shouldPassConfigurationToCameraDeviceAndWriter { timeout:1 enforceOrder:YES]; - [camera startVideoRecordingWithResult:^(id _Nullable result){ - - }]; + [camera + startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + } + messengerForStreaming:nil]; [self waitForExpectations:@[ injectedWrapper.audioSettingsExpectation, injectedWrapper.videoSettingsExpectation @@ -300,28 +179,25 @@ - (void)testSettings_ShouldBeSupportedByMethodCall { OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); // Set up method call - FlutterMethodCall *call = - [FlutterMethodCall methodCallWithMethodName:@"create" - arguments:@{ - @"resolutionPreset" : @(gTestResolutionPreset), - @"enableAudio" : @(gTestEnableAudio), - @"fps" : @(gTestFramesPerSecond), - @"videoBitrate" : @(gTestVideoBitrate), - @"audioBitrate" : @(gTestAudioBitrate) - }]; - - __block id resultValue; - [camera createCameraOnSessionQueueWithCreateMethodCall:call - result:^(id _Nullable result) { - resultValue = result; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; + FCPPlatformMediaSettings *mediaSettings = + [FCPPlatformMediaSettings makeWithResolutionPreset:gTestResolutionPreset + framesPerSecond:@(gTestFramesPerSecond) + videoBitrate:@(gTestVideoBitrate) + audioBitrate:@(gTestAudioBitrate) + enableAudio:gTestEnableAudio]; + + __block NSNumber *resultValue; + [camera createCameraOnSessionQueueWithName:@"acamera" + settings:mediaSettings + completion:^(NSNumber *result, FlutterError *error) { + XCTAssertNil(error); + resultValue = result; + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; // Verify the result - NSDictionary *dictionaryResult = (NSDictionary *)resultValue; - XCTAssertNotNil(dictionaryResult); - XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]); + XCTAssertNotNil(resultValue); } @end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 57c47ac1fdd..eded154995e 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -14,7 +14,8 @@ NS_ASSUME_NONNULL_BEGIN /// dependency injection). /// @return an FLTCam object. extern FLTCam *_Nullable FLTCreateCamWithCaptureSessionQueueAndMediaSettings( - dispatch_queue_t _Nullable captureSessionQueue, FLTCamMediaSettings *_Nullable mediaSettings, + dispatch_queue_t _Nullable captureSessionQueue, + FCPPlatformMediaSettings *_Nullable mediaSettings, FLTCamMediaSettingsAVWrapper *_Nullable mediaSettingsAVWrapper); extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue); @@ -24,7 +25,7 @@ extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessi /// @param resolutionPreset preset for camera's captureSession resolution /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, - NSString *resolutionPreset); + FCPPlatformResolutionPreset resolutionPreset); /// Creates an `FLTCam` with a given captureSession and resolutionPreset. /// Allows to inject a capture device and a block to compute the video dimensions. @@ -34,8 +35,8 @@ extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSess /// @param videoDimensionsForFormat custom code to determine video dimensions /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( - AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, - VideoDimensionsForFormat videoDimensionsForFormat); + AVCaptureSession *captureSession, FCPPlatformResolutionPreset resolutionPreset, + AVCaptureDevice *captureDevice, VideoDimensionsForFormat videoDimensionsForFormat); /// Creates a test sample buffer. /// @return a test sample buffer. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index d334576e212..0dac5c4a59b 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -3,21 +3,29 @@ // found in the LICENSE file. #import "CameraTestUtils.h" + #import @import AVFoundation; +@import camera_avfoundation; + +static FCPPlatformMediaSettings *FCPGetDefaultMediaSettings( + FCPPlatformResolutionPreset resolutionPreset) { + return [FCPPlatformMediaSettings makeWithResolutionPreset:resolutionPreset + framesPerSecond:nil + videoBitrate:nil + audioBitrate:nil + enableAudio:YES]; +} FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue) { return FLTCreateCamWithCaptureSessionQueueAndMediaSettings(captureSessionQueue, nil, nil); } FLTCam *FLTCreateCamWithCaptureSessionQueueAndMediaSettings( - dispatch_queue_t captureSessionQueue, FLTCamMediaSettings *mediaSettings, + dispatch_queue_t captureSessionQueue, FCPPlatformMediaSettings *mediaSettings, FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper) { if (!mediaSettings) { - mediaSettings = [[FLTCamMediaSettings alloc] initWithFramesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:true]; + mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPresetMedium); } if (!mediaSettingsAVWrapper) { @@ -44,7 +52,6 @@ OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); id fltCam = [[FLTCam alloc] initWithCameraName:@"camera" - resolutionPreset:@"medium" mediaSettings:mediaSettings mediaSettingsAVWrapper:mediaSettingsAVWrapper orientation:UIDeviceOrientationPortrait @@ -82,7 +89,7 @@ } FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, - NSString *resolutionPreset) { + FCPPlatformResolutionPreset resolutionPreset) { id inputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); @@ -91,24 +98,19 @@ OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - return - [[FLTCam alloc] initWithCameraName:@"camera" - resolutionPreset:resolutionPreset - mediaSettings:[[FLTCamMediaSettings alloc] initWithFramesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:true] - mediaSettingsAVWrapper:[[FLTCamMediaSettingsAVWrapper alloc] init] - orientation:UIDeviceOrientationPortrait - videoCaptureSession:captureSession - audioCaptureSession:audioSessionMock - captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - error:nil]; + return [[FLTCam alloc] initWithCameraName:@"camera" + mediaSettings:FCPGetDefaultMediaSettings(resolutionPreset) + mediaSettingsAVWrapper:[[FLTCamMediaSettingsAVWrapper alloc] init] + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + error:nil]; } FLTCam *FLTCreateCamWithVideoDimensionsForFormat( - AVCaptureSession *captureSession, NSString *resolutionPreset, AVCaptureDevice *captureDevice, - VideoDimensionsForFormat videoDimensionsForFormat) { + AVCaptureSession *captureSession, FCPPlatformResolutionPreset resolutionPreset, + AVCaptureDevice *captureDevice, VideoDimensionsForFormat videoDimensionsForFormat) { id inputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); @@ -117,22 +119,17 @@ OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - return [[FLTCam alloc] - initWithResolutionPreset:resolutionPreset - mediaSettings:[[FLTCamMediaSettings alloc] initWithFramesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:true] - mediaSettingsAVWrapper:[[FLTCamMediaSettingsAVWrapper alloc] init] - orientation:UIDeviceOrientationPortrait - videoCaptureSession:captureSession - audioCaptureSession:audioSessionMock - captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - captureDeviceFactory:^AVCaptureDevice *(void) { - return captureDevice; - } - videoDimensionsForFormat:videoDimensionsForFormat - error:nil]; + return [[FLTCam alloc] initWithMediaSettings:FCPGetDefaultMediaSettings(resolutionPreset) + mediaSettingsAVWrapper:[[FLTCamMediaSettingsAVWrapper alloc] init] + orientation:UIDeviceOrientationPortrait + videoCaptureSession:captureSession + audioCaptureSession:audioSessionMock + captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) + captureDeviceFactory:^AVCaptureDevice *(void) { + return captureDevice; + } + videoDimensionsForFormat:videoDimensionsForFormat + error:nil]; } CMSampleBufferRef FLTCreateTestSampleBuffer(void) { diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m index 00c583d2412..f81625f849f 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m @@ -45,8 +45,9 @@ - (void)testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsW // `FLTCam::captureToFile` runs on capture session queue. dispatch_async(captureSessionQueue, ^{ - [cam captureToFile:^(id _Nullable result) { - XCTAssertTrue([result isKindOfClass:[FlutterError class]]); + [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { + XCTAssertNil(result); + XCTAssertNotNil(error); [errorExpectation fulfill]; }]; }); @@ -84,7 +85,7 @@ - (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWi // `FLTCam::captureToFile` runs on capture session queue. dispatch_async(captureSessionQueue, ^{ - [cam captureToFile:^(id _Nullable result) { + [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { XCTAssertEqual(result, filePath); [pathExpectation fulfill]; }]; @@ -100,7 +101,7 @@ - (void)testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndF dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); - [cam setImageFileFormat:FCPFileFormatHEIF]; + [cam setImageFileFormat:FCPPlatformImageFileFormatHeif]; AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey : AVVideoCodecTypeHEVC}]; @@ -125,8 +126,7 @@ - (void)testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndF cam.capturePhotoOutput = mockOutput; // `FLTCam::captureToFile` runs on capture session queue. dispatch_async(captureSessionQueue, ^{ - [cam captureToFile:^(id _Nullable result) { - NSString *filePath = (NSString *)result; + [cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) { XCTAssertEqualObjects([filePath pathExtension], @"heif"); [expectation fulfill]; }]; @@ -142,7 +142,7 @@ - (void)testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndF dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); - [cam setImageFileFormat:FCPFileFormatHEIF]; + [cam setImageFileFormat:FCPPlatformImageFileFormatHeif]; AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; id mockSettings = OCMClassMock([AVCapturePhotoSettings class]); @@ -162,8 +162,7 @@ - (void)testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndF cam.capturePhotoOutput = mockOutput; // `FLTCam::captureToFile` runs on capture session queue. dispatch_async(captureSessionQueue, ^{ - [cam captureToFile:^(id _Nullable result) { - NSString *filePath = (NSString *)result; + [cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) { XCTAssertEqualObjects([filePath pathExtension], @"jpg"); [expectation fulfill]; }]; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m index 9c036adeaca..cba488dfe5b 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m @@ -55,12 +55,12 @@ - (void)testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPause }); // Pause then resume the recording. - [cam startVideoRecordingWithResult:^(id _Nullable result){ - }]; - [cam pauseVideoRecordingWithResult:^(id _Nullable result){ - }]; - [cam resumeVideoRecordingWithResult:^(id _Nullable result){ - }]; + [cam + startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + } + messengerForStreaming:nil]; + [cam pauseVideoRecording]; + [cam resumeVideoRecording]; [cam captureOutput:cam.captureVideoOutput didOutputSampleBuffer:sampleBuffer @@ -111,8 +111,10 @@ - (void)testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples { writtenSamples = [writtenSamples arrayByAddingObject:@"audio"]; }); - [cam startVideoRecordingWithResult:^(id _Nullable result){ - }]; + [cam + startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + } + messengerForStreaming:nil]; [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m deleted file mode 100644 index f91896b5ff5..00000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m +++ /dev/null @@ -1,109 +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 camera_avfoundation; -@import camera_avfoundation.Test; -@import XCTest; -#import - -@interface ThreadSafeTextureRegistryTests : XCTestCase -@end - -@implementation ThreadSafeTextureRegistryTests - -- (void)testShouldStayOnMainThreadIfCalledFromMainThread { - NSObject *mockTextureRegistry = - OCMProtocolMock(@protocol(FlutterTextureRegistry)); - FLTThreadSafeTextureRegistry *threadSafeTextureRegistry = - [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:mockTextureRegistry]; - - XCTestExpectation *registerTextureExpectation = - [self expectationWithDescription:@"registerTexture must be called on the main thread"]; - XCTestExpectation *unregisterTextureExpectation = - [self expectationWithDescription:@"unregisterTexture must be called on the main thread"]; - XCTestExpectation *textureFrameAvailableExpectation = - [self expectationWithDescription:@"textureFrameAvailable must be called on the main thread"]; - XCTestExpectation *registerTextureCompletionExpectation = - [self expectationWithDescription: - @"registerTexture's completion block must be called on the main thread"]; - - OCMStub([mockTextureRegistry registerTexture:[OCMArg any]]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [registerTextureExpectation fulfill]; - } - }); - - OCMStub([mockTextureRegistry unregisterTexture:0]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [unregisterTextureExpectation fulfill]; - } - }); - - OCMStub([mockTextureRegistry textureFrameAvailable:0]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [textureFrameAvailableExpectation fulfill]; - } - }); - - NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); - [threadSafeTextureRegistry registerTexture:anyTexture - completion:^(int64_t textureId) { - if (NSThread.isMainThread) { - [registerTextureCompletionExpectation fulfill]; - } - }]; - [threadSafeTextureRegistry textureFrameAvailable:0]; - [threadSafeTextureRegistry unregisterTexture:0]; - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)testShouldDispatchToMainThreadIfCalledFromBackgroundThread { - NSObject *mockTextureRegistry = - OCMProtocolMock(@protocol(FlutterTextureRegistry)); - FLTThreadSafeTextureRegistry *threadSafeTextureRegistry = - [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:mockTextureRegistry]; - - XCTestExpectation *registerTextureExpectation = - [self expectationWithDescription:@"registerTexture must be called on the main thread"]; - XCTestExpectation *unregisterTextureExpectation = - [self expectationWithDescription:@"unregisterTexture must be called on the main thread"]; - XCTestExpectation *textureFrameAvailableExpectation = - [self expectationWithDescription:@"textureFrameAvailable must be called on the main thread"]; - XCTestExpectation *registerTextureCompletionExpectation = - [self expectationWithDescription: - @"registerTexture's completion block must be called on the main thread"]; - - OCMStub([mockTextureRegistry registerTexture:[OCMArg any]]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [registerTextureExpectation fulfill]; - } - }); - - OCMStub([mockTextureRegistry unregisterTexture:0]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [unregisterTextureExpectation fulfill]; - } - }); - - OCMStub([mockTextureRegistry textureFrameAvailable:0]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [textureFrameAvailableExpectation fulfill]; - } - }); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); - [threadSafeTextureRegistry registerTexture:anyTexture - completion:^(int64_t textureId) { - if (NSThread.isMainThread) { - [registerTextureCompletionExpectation fulfill]; - } - }]; - [threadSafeTextureRegistry textureFrameAvailable:0]; - [threadSafeTextureRegistry unregisterTexture:0]; - }); - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -@end diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m index 90a124ebafd..de89aecce22 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m @@ -6,12 +6,12 @@ #import "CameraPlugin_Test.h" @import AVFoundation; +@import Flutter; #import "CameraPermissionUtils.h" #import "CameraProperties.h" #import "FLTCam.h" #import "FLTThreadSafeEventChannel.h" -#import "FLTThreadSafeTextureRegistry.h" #import "QueueUtils.h" #import "messages.g.h" @@ -22,7 +22,7 @@ } @interface CameraPlugin () -@property(readonly, nonatomic) FLTThreadSafeTextureRegistry *registry; +@property(readonly, nonatomic) id registry; @property(readonly, nonatomic) NSObject *messenger; @property(nonatomic) FCPCameraGlobalEventApi *globalEventAPI; @end @@ -30,12 +30,8 @@ @interface CameraPlugin () @implementation CameraPlugin + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera_avfoundation" - binaryMessenger:[registrar messenger]]; CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] messenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; SetUpFCPCameraApi([registrar messenger], instance); } @@ -52,7 +48,7 @@ - (instancetype)initWithRegistry:(NSObject *)registry globalAPI:(FCPCameraGlobalEventApi *)globalAPI { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:registry]; + _registry = registry; _messenger = messenger; _globalEventAPI = globalAPI; _captureSessionQueue = dispatch_queue_create("io.flutter.camera.captureSessionQueue", NULL); @@ -103,13 +99,7 @@ - (void)sendDeviceOrientation:(UIDeviceOrientation)orientation { }); } -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - // Invoke the plugin on another dispatch queue to avoid blocking the UI. - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf handleMethodCallAsync:call result:result]; - }); -} +#pragma mark FCPCameraApi Implementation - (void)availableCamerasWithCompletion: (nonnull void (^)(NSArray *_Nullable, @@ -148,274 +138,355 @@ - (void)availableCamerasWithCompletion: }); } -- (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"create" isEqualToString:call.method]) { - [self handleCreateMethodCall:call result:result]; - } else if ([@"startImageStream" isEqualToString:call.method]) { - [_camera startImageStreamWithMessenger:_messenger]; - result(nil); - } else if ([@"stopImageStream" isEqualToString:call.method]) { - [_camera stopImageStream]; - result(nil); - } else if ([@"receivedImageStreamData" isEqualToString:call.method]) { - [_camera receivedImageStreamData]; - result(nil); - } else { - NSDictionary *argsMap = call.arguments; - NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; - if ([@"initialize" isEqualToString:call.method]) { - NSString *videoFormatValue = ((NSString *)argsMap[@"imageFormatGroup"]); - - [_camera setVideoFormat:FLTGetVideoFormatFromString(videoFormatValue)]; +- (void)createCameraWithName:(nonnull NSString *)cameraName + settings:(nonnull FCPPlatformMediaSettings *)settings + completion: + (nonnull void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { + // Create FLTCam only if granted camera access (and audio access if audio is enabled) + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + typeof(self) strongSelf = weakSelf; + if (!strongSelf) return; - __weak CameraPlugin *weakSelf = self; - _camera.onFrameAvailable = ^{ - if (![weakSelf.camera isPreviewPaused]) { - [weakSelf.registry textureFrameAvailable:cameraId]; - } - }; - _camera.dartAPI = [[FCPCameraEventApi alloc] - initWithBinaryMessenger:_messenger - messageChannelSuffix:[NSString stringWithFormat:@"%ld", cameraId]]; - [_camera reportInitializationState]; - [self sendDeviceOrientation:[UIDevice currentDevice].orientation]; - [_camera start]; - result(nil); - } else if ([@"takePicture" isEqualToString:call.method]) { - [_camera captureToFile:result]; - } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:cameraId]; - [_camera close]; - result(nil); - } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { - [self.camera setUpCaptureSessionForAudio]; - result(nil); - } else if ([@"startVideoRecording" isEqualToString:call.method]) { - BOOL enableStream = [call.arguments[@"enableStream"] boolValue]; - if (enableStream) { - [_camera startVideoRecordingWithResult:result messengerForStreaming:_messenger]; + if (error) { + completion(nil, error); } else { - [_camera startVideoRecordingWithResult:result]; - } - } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result]; - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecordingWithResult:result]; - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecordingWithResult:result]; - } else if ([@"getMaxZoomLevel" isEqualToString:call.method]) { - [_camera getMaxZoomLevelWithResult:result]; - } else if ([@"getMinZoomLevel" isEqualToString:call.method]) { - [_camera getMinZoomLevelWithResult:result]; - } else if ([@"setZoomLevel" isEqualToString:call.method]) { - CGFloat zoom = ((NSNumber *)argsMap[@"zoom"]).floatValue; - [_camera setZoomLevel:zoom Result:result]; - } else if ([@"setFlashMode" isEqualToString:call.method]) { - [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; - } else if ([@"setExposureMode" isEqualToString:call.method]) { - [_camera setExposureModeWithResult:result mode:call.arguments[@"mode"]]; - } else if ([@"setExposurePoint" isEqualToString:call.method]) { - BOOL reset = ((NSNumber *)call.arguments[@"reset"]).boolValue; - double x = 0.5; - double y = 0.5; - if (!reset) { - x = ((NSNumber *)call.arguments[@"x"]).doubleValue; - y = ((NSNumber *)call.arguments[@"y"]).doubleValue; - } - [_camera setExposurePointWithResult:result x:x y:y]; - } else if ([@"getMinExposureOffset" isEqualToString:call.method]) { - result(@(_camera.captureDevice.minExposureTargetBias)); - } else if ([@"getMaxExposureOffset" isEqualToString:call.method]) { - result(@(_camera.captureDevice.maxExposureTargetBias)); - } else if ([@"getExposureOffsetStepSize" isEqualToString:call.method]) { - result(@(0.0)); - } else if ([@"setExposureOffset" isEqualToString:call.method]) { - [_camera setExposureOffsetWithResult:result - offset:((NSNumber *)call.arguments[@"offset"]).doubleValue]; - } else if ([@"lockCaptureOrientation" isEqualToString:call.method]) { - [_camera lockCaptureOrientationWithResult:result orientation:call.arguments[@"orientation"]]; - } else if ([@"unlockCaptureOrientation" isEqualToString:call.method]) { - [_camera unlockCaptureOrientationWithResult:result]; - } else if ([@"setFocusMode" isEqualToString:call.method]) { - [_camera setFocusModeWithResult:result mode:call.arguments[@"mode"]]; - } else if ([@"setFocusPoint" isEqualToString:call.method]) { - BOOL reset = ((NSNumber *)call.arguments[@"reset"]).boolValue; - double x = 0.5; - double y = 0.5; - if (!reset) { - x = ((NSNumber *)call.arguments[@"x"]).doubleValue; - y = ((NSNumber *)call.arguments[@"y"]).doubleValue; + // Request audio permission on `create` call with `enableAudio` argument instead of the + // `prepareForVideoRecording` call. This is because `prepareForVideoRecording` call is + // optional, and used as a workaround to fix a missing frame issue on iOS. + if (settings.enableAudio) { + // Setup audio capture session only if granted audio access. + FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + // cannot use the outter `strongSelf` + typeof(self) strongSelf = weakSelf; + if (!strongSelf) return; + if (error) { + completion(nil, error); + } else { + [strongSelf createCameraOnSessionQueueWithName:cameraName + settings:settings + completion:completion]; + } + }); + } else { + [strongSelf createCameraOnSessionQueueWithName:cameraName + settings:settings + completion:completion]; + } } - [_camera setFocusPointWithResult:result x:x y:y]; - } else if ([@"pausePreview" isEqualToString:call.method]) { - [_camera pausePreviewWithResult:result]; - } else if ([@"resumePreview" isEqualToString:call.method]) { - [_camera resumePreviewWithResult:result]; - } else if ([@"setDescriptionWhileRecording" isEqualToString:call.method]) { - [_camera setDescriptionWhileRecording:(call.arguments[@"cameraName"]) result:result]; - } else if ([@"setImageFileFormat" isEqualToString:call.method]) { - NSString *fileFormat = call.arguments[@"fileFormat"]; - [_camera setImageFileFormat:FCPGetFileFormatFromString(fileFormat)]; - } else { - result(FlutterMethodNotImplemented); - } - } + }); + }); } -- (void)handleCreateMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - // Create FLTCam only if granted camera access (and audio access if audio is enabled) +- (void)initializeCamera:(NSInteger)cameraId + withImageFormat:(FCPPlatformImageFormatGroup)imageFormat + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf sessionQueueInitializeCamera:cameraId + withImageFormat:imageFormat + completion:completion]; + }); +} + +- (void)startImageStreamWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera startImageStreamWithMessenger:weakSelf.messenger]; + completion(nil); + }); +} + +- (void)stopImageStreamWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera stopImageStream]; + completion(nil); + }); +} + +- (void)receivedImageStreamDataWithCompletion: + (nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera receivedImageStreamData]; + completion(nil); + }); +} + +- (void)takePictureWithCompletion:(nonnull void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera captureToFileWithCompletion:completion]; + }); +} + +- (void)prepareForVideoRecordingWithCompletion: + (nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setUpCaptureSessionForAudio]; + completion(nil); + }); +} + +- (void)startVideoRecordingWithStreaming:(BOOL)enableStream + completion:(nonnull void (^)(FlutterError *_Nullable))completion { __weak typeof(self) weakSelf = self; - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + dispatch_async(self.captureSessionQueue, ^{ typeof(self) strongSelf = weakSelf; if (!strongSelf) return; + [strongSelf.camera + startVideoRecordingWithCompletion:completion + messengerForStreaming:(enableStream ? strongSelf.messenger : nil)]; + }); +} - if (error) { - result(error); - } else { - // Request audio permission on `create` call with `enableAudio` argument instead of the - // `prepareForVideoRecording` call. This is because `prepareForVideoRecording` call is - // optional, and used as a workaround to fix a missing frame issue on iOS. - BOOL audioEnabled = [call.arguments[@"enableAudio"] boolValue]; - if (audioEnabled) { - // Setup audio capture session only if granted audio access. - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { - // cannot use the outter `strongSelf` - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - if (error) { - result(error); - } else { - [strongSelf createCameraOnSessionQueueWithCreateMethodCall:call result:result]; - } - }); - } else { - [strongSelf createCameraOnSessionQueueWithCreateMethodCall:call result:result]; - } - } +- (void)stopVideoRecordingWithCompletion:(nonnull void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera stopVideoRecordingWithCompletion:completion]; }); } -// Returns number value if provided and positive, or nil. -// Used to parse values like framerates and bitrates, that are positive by nature. -// nil allows to ignore unsupported values. -+ (NSNumber *)positiveNumberValueOrNilForArgument:(NSString *)argument - fromMethod:(FlutterMethodCall *)flutterMethodCall - error:(NSError **)error { - id value = flutterMethodCall.arguments[argument]; +- (void)pauseVideoRecordingWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera pauseVideoRecording]; + completion(nil); + }); +} - if (!value || [value isEqual:[NSNull null]]) { - return nil; - } +- (void)resumeVideoRecordingWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera resumeVideoRecording]; + completion(nil); + }); +} - if (![value isKindOfClass:[NSNumber class]]) { - if (error) { - *error = [NSError errorWithDomain:@"ArgumentError" - code:0 - userInfo:@{ - NSLocalizedDescriptionKey : - [NSString stringWithFormat:@"%@ should be a number", argument] - }]; - } - return nil; - } +- (void)getMinimumZoomLevel:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + completion(@(weakSelf.camera.minimumAvailableZoomFactor), nil); + }); +} - NSNumber *number = (NSNumber *)value; +- (void)getMaximumZoomLevel:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + completion(@(weakSelf.camera.maximumAvailableZoomFactor), nil); + }); +} - if (isnan([number doubleValue])) { - if (error) { - *error = [NSError errorWithDomain:@"ArgumentError" - code:0 - userInfo:@{ - NSLocalizedDescriptionKey : - [NSString stringWithFormat:@"%@ should not be a nan", argument] - }]; - } - return nil; - } +- (void)setZoomLevel:(double)zoom completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setZoomLevel:zoom withCompletion:completion]; + }); +} - if ([number doubleValue] <= 0.0) { - if (error) { - *error = [NSError errorWithDomain:@"ArgumentError" - code:0 - userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"%@ should be a positive number", argument] - }]; - } - return nil; - } +- (void)setFlashMode:(FCPPlatformFlashMode)mode + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setFlashMode:mode withCompletion:completion]; + }); +} - return number; +- (void)setExposureMode:(FCPPlatformExposureMode)mode + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setExposureMode:mode]; + completion(nil); + }); } -- (void)createCameraOnSessionQueueWithCreateMethodCall:(FlutterMethodCall *)createMethodCall - result:(FlutterResult)result { +- (void)setExposurePoint:(nullable FCPPlatformPoint *)point + completion:(nonnull void (^)(FlutterError *_Nullable))completion { __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; + [weakSelf.camera setExposurePoint:point withCompletion:completion]; + }); +} - NSString *cameraName = createMethodCall.arguments[@"cameraName"]; +- (void)getMinimumExposureOffset:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + completion(@(weakSelf.camera.captureDevice.minExposureTargetBias), nil); + }); +} - NSError *error; +- (void)getMaximumExposureOffset:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + completion(@(weakSelf.camera.captureDevice.maxExposureTargetBias), nil); + }); +} - NSNumber *framesPerSecond = [CameraPlugin positiveNumberValueOrNilForArgument:@"fps" - fromMethod:createMethodCall - error:&error]; - if (error) { - result(FlutterErrorFromNSError(error)); - return; - } +- (void)setExposureOffset:(double)offset + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setExposureOffset:offset]; + completion(nil); + }); +} - NSNumber *videoBitrate = [CameraPlugin positiveNumberValueOrNilForArgument:@"videoBitrate" - fromMethod:createMethodCall - error:&error]; - if (error) { - result(FlutterErrorFromNSError(error)); - return; - } +- (void)setFocusMode:(FCPPlatformFocusMode)mode + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setFocusMode:mode]; + completion(nil); + }); +} - NSNumber *audioBitrate = [CameraPlugin positiveNumberValueOrNilForArgument:@"audioBitrate" - fromMethod:createMethodCall - error:&error]; - if (error) { - result(FlutterErrorFromNSError(error)); - return; - } +- (void)setFocusPoint:(nullable FCPPlatformPoint *)point + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setFocusPoint:point withCompletion:completion]; + }); +} - NSString *resolutionPreset = createMethodCall.arguments[@"resolutionPreset"]; - NSNumber *enableAudio = createMethodCall.arguments[@"enableAudio"]; - FLTCamMediaSettings *mediaSettings = - [[FLTCamMediaSettings alloc] initWithFramesPerSecond:framesPerSecond - videoBitrate:videoBitrate - audioBitrate:audioBitrate - enableAudio:[enableAudio boolValue]]; - FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper = - [[FLTCamMediaSettingsAVWrapper alloc] init]; - - FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName - resolutionPreset:resolutionPreset - mediaSettings:mediaSettings - mediaSettingsAVWrapper:mediaSettingsAVWrapper - orientation:[[UIDevice currentDevice] orientation] - captureSessionQueue:strongSelf.captureSessionQueue - error:&error]; - - if (error) { - result(FlutterErrorFromNSError(error)); - } else { - if (strongSelf.camera) { - [strongSelf.camera close]; - } - strongSelf.camera = cam; - [strongSelf.registry registerTexture:cam - completion:^(int64_t textureId) { - result(@{ - @"cameraId" : @(textureId), - }); - }]; +- (void)lockCaptureOrientation:(FCPPlatformDeviceOrientation)orientation + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera lockCaptureOrientation:orientation]; + completion(nil); + }); +} + +- (void)unlockCaptureOrientationWithCompletion: + (nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera unlockCaptureOrientation]; + completion(nil); + }); +} + +- (void)pausePreviewWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera pausePreview]; + completion(nil); + }); +} + +- (void)resumePreviewWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera resumePreview]; + completion(nil); + }); +} + +- (void)setImageFileFormat:(FCPPlatformImageFileFormat)format + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setImageFileFormat:format]; + completion(nil); + }); +} + +- (void)updateDescriptionWhileRecordingCameraName:(nonnull NSString *)cameraName + completion: + (nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setDescriptionWhileRecording:cameraName withCompletion:completion]; + }); +} + +- (void)disposeCamera:(NSInteger)cameraId + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + [_registry unregisterTexture:cameraId]; + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera close]; + completion(nil); + }); +} + +#pragma mark Private + +// This must be called on captureSessionQueue. It is extracted from +// initializeCamera:withImageFormat:completion: to make it easier to reason about strong/weak +// self pointers. +- (void)sessionQueueInitializeCamera:(NSInteger)cameraId + withImageFormat:(FCPPlatformImageFormatGroup)imageFormat + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + [_camera setVideoFormat:FCPGetPixelFormatForPigeonFormat(imageFormat)]; + + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + typeof(self) strongSelf = weakSelf; + if (!strongSelf) return; + if (![strongSelf.camera isPreviewPaused]) { + FLTEnsureToRunOnMainQueue(^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }); } + }; + _camera.dartAPI = [[FCPCameraEventApi alloc] + initWithBinaryMessenger:_messenger + messageChannelSuffix:[NSString stringWithFormat:@"%ld", cameraId]]; + [_camera reportInitializationState]; + [self sendDeviceOrientation:[UIDevice currentDevice].orientation]; + [_camera start]; + completion(nil); +} + +- (void)createCameraOnSessionQueueWithName:(NSString *)name + settings:(FCPPlatformMediaSettings *)settings + completion:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf sessionQueueCreateCameraWithName:name settings:settings completion:completion]; }); } +// This must be called on captureSessionQueue. It is extracted from +// initializeCamera:withImageFormat:completion: to make it easier to reason about strong/weak +// self pointers. +- (void)sessionQueueCreateCameraWithName:(NSString *)name + settings:(FCPPlatformMediaSettings *)settings + completion:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper = + [[FLTCamMediaSettingsAVWrapper alloc] init]; + + NSError *error; + FLTCam *cam = [[FLTCam alloc] initWithCameraName:name + mediaSettings:settings + mediaSettingsAVWrapper:mediaSettingsAVWrapper + orientation:[[UIDevice currentDevice] orientation] + captureSessionQueue:self.captureSessionQueue + error:&error]; + + if (error) { + completion(nil, FlutterErrorFromNSError(error)); + } else { + [_camera close]; + _camera = cam; + __weak typeof(self) weakSelf = self; + FLTEnsureToRunOnMainQueue(^{ + completion(@([weakSelf.registry registerTexture:cam]), nil); + }); + } +} + @end diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.modulemap b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.modulemap index 65a82b70bc2..bc864d17492 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.modulemap +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.modulemap @@ -12,7 +12,6 @@ framework module camera_avfoundation { header "FLTCam_Test.h" header "FLTSavePhotoDelegate_Test.h" header "FLTThreadSafeEventChannel.h" - header "FLTThreadSafeTextureRegistry.h" header "QueueUtils.h" } } diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin_Test.h b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin_Test.h index ab6fb186ad7..c29c2f306db 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin_Test.h +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin_Test.h @@ -30,13 +30,6 @@ /// Hide the default public constructor. - (instancetype)init NS_UNAVAILABLE; -/// Handles `FlutterMethodCall`s and ensures result is send on the main dispatch queue. -/// -/// @param call The method call command object. -/// @param result A wrapper around the `FlutterResult` callback which ensures the callback is called -/// on the main dispatch queue. -- (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result; - /// Called by the @c NSNotificationManager each time the device's orientation is changed. /// /// @param notification @c NSNotification instance containing a reference to the `UIDevice` object @@ -44,8 +37,10 @@ - (void)orientationChanged:(NSNotification *)notification; /// Creates FLTCam on session queue and reports the creation result. -/// @param createMethodCall the create method call -/// @param result a thread safe flutter result wrapper object to report creation result. -- (void)createCameraOnSessionQueueWithCreateMethodCall:(FlutterMethodCall *)createMethodCall - result:(FlutterResult)result; +/// @param name the name of the camera. +/// @param settings the creation settings. +/// @param completion the callback to inform the Dart side of the plugin of creation. +- (void)createCameraOnSessionQueueWithName:(NSString *)name + settings:(FCPPlatformMediaSettings *)settings + completion:(void (^)(NSNumber *, FlutterError *))completion; @end diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h index e19f98faa2a..ea7a4a3438a 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h @@ -9,83 +9,19 @@ NS_ASSUME_NONNULL_BEGIN -#pragma mark - flash mode - -/// Represents camera's flash mode. Mirrors `FlashMode` enum in flash_mode.dart. -typedef NS_ENUM(NSInteger, FLTFlashMode) { - FLTFlashModeOff, - FLTFlashModeAuto, - FLTFlashModeAlways, - FLTFlashModeTorch, - // This should never occur; it indicates an unknown value was received over - // the platform channel. - FLTFlashModeInvalid, -}; - -/// Gets FLTFlashMode from its string representation. -/// @param mode a string representation of the FLTFlashMode. -extern FLTFlashMode FLTGetFLTFlashModeForString(NSString *mode); - /// Gets AVCaptureFlashMode from FLTFlashMode. /// @param mode flash mode. -extern AVCaptureFlashMode FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashMode mode); - -#pragma mark - exposure mode - -/// Gets FCPPlatformExposureMode from its string representation. -/// @param mode a string representation of the exposure mode. -extern FCPPlatformExposureMode FCPGetExposureModeForString(NSString *mode); - -#pragma mark - focus mode - -/// Gets FCPPlatformFocusMode from its string representation. -/// @param mode a string representation of focus mode. -extern FCPPlatformFocusMode FCPGetFocusModeForString(NSString *mode); - -#pragma mark - device orientation +extern AVCaptureFlashMode FCPGetAVCaptureFlashModeForPigeonFlashMode(FCPPlatformFlashMode mode); -/// Gets UIDeviceOrientation from its string representation. -extern UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation); +/// Gets UIDeviceOrientation from its Pigeon representation. +extern UIDeviceOrientation FCPGetUIDeviceOrientationForPigeonDeviceOrientation( + FCPPlatformDeviceOrientation orientation); /// Gets a Pigeon representation of UIDeviceOrientation. extern FCPPlatformDeviceOrientation FCPGetPigeonDeviceOrientationForOrientation( UIDeviceOrientation orientation); -#pragma mark - resolution preset - -/// Represents camera's resolution present. Mirrors ResolutionPreset in camera.dart. -typedef NS_ENUM(NSInteger, FLTResolutionPreset) { - FLTResolutionPresetVeryLow, - FLTResolutionPresetLow, - FLTResolutionPresetMedium, - FLTResolutionPresetHigh, - FLTResolutionPresetVeryHigh, - FLTResolutionPresetUltraHigh, - FLTResolutionPresetMax, - // This should never occur; it indicates an unknown value was received over - // the platform channel. - FLTResolutionPresetInvalid, -}; - -/// Gets FLTResolutionPreset from its string representation. -/// @param preset a string representation of FLTResolutionPreset. -extern FLTResolutionPreset FLTGetFLTResolutionPresetForString(NSString *preset); - -#pragma mark - video format - -/// Gets VideoFormat from its string representation. -extern OSType FLTGetVideoFormatFromString(NSString *videoFormatString); - -/// Represents image format. Mirrors ImageFileFormat in camera.dart. -typedef NS_ENUM(NSInteger, FCPFileFormat) { - FCPFileFormatJPEG, - FCPFileFormatHEIF, - FCPFileFormatInvalid, -}; - -#pragma mark - image extension - -/// Gets a string representation of ImageFileFormat. -extern FCPFileFormat FCPGetFileFormatFromString(NSString *fileFormatString); +/// Gets VideoFormat from its Pigeon representation. +extern OSType FCPGetPixelFormatForPigeonFormat(FCPPlatformImageFormatGroup imageFormat); NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m index e068c186474..5aa1f25bb03 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m @@ -4,78 +4,32 @@ #import "CameraProperties.h" -#pragma mark - flash mode - -FLTFlashMode FLTGetFLTFlashModeForString(NSString *mode) { - if ([mode isEqualToString:@"off"]) { - return FLTFlashModeOff; - } else if ([mode isEqualToString:@"auto"]) { - return FLTFlashModeAuto; - } else if ([mode isEqualToString:@"always"]) { - return FLTFlashModeAlways; - } else if ([mode isEqualToString:@"torch"]) { - return FLTFlashModeTorch; - } else { - return FLTFlashModeInvalid; - } -} - -AVCaptureFlashMode FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashMode mode) { +AVCaptureFlashMode FCPGetAVCaptureFlashModeForPigeonFlashMode(FCPPlatformFlashMode mode) { switch (mode) { - case FLTFlashModeOff: + case FCPPlatformFlashModeOff: return AVCaptureFlashModeOff; - case FLTFlashModeAuto: + case FCPPlatformFlashModeAuto: return AVCaptureFlashModeAuto; - case FLTFlashModeAlways: + case FCPPlatformFlashModeAlways: return AVCaptureFlashModeOn; - case FLTFlashModeTorch: - default: + case FCPPlatformFlashModeTorch: + NSCAssert(false, @"This mode cannot be converted, and requires custom handling."); return -1; } } -#pragma mark - exposure mode - -FCPPlatformExposureMode FCPGetExposureModeForString(NSString *mode) { - if ([mode isEqualToString:@"auto"]) { - return FCPPlatformExposureModeAuto; - } else if ([mode isEqualToString:@"locked"]) { - return FCPPlatformExposureModeLocked; - } else { - // This should be unreachable; see _serializeExposureMode in avfoundation_camera.dart. - NSCAssert(false, @"Unsupported exposure mode"); - return FCPPlatformExposureModeAuto; - } -} - -#pragma mark - focus mode - -FCPPlatformFocusMode FCPGetFocusModeForString(NSString *mode) { - if ([mode isEqualToString:@"auto"]) { - return FCPPlatformFocusModeAuto; - } else if ([mode isEqualToString:@"locked"]) { - return FCPPlatformFocusModeLocked; - } else { - // This should be unreachable; see _serializeFocusMode in avfoundation_camera.dart. - NSCAssert(false, @"Unsupported focus mode"); - return FCPPlatformFocusModeAuto; - } -} - -#pragma mark - device orientation - -UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation) { - if ([orientation isEqualToString:@"portraitDown"]) { - return UIDeviceOrientationPortraitUpsideDown; - } else if ([orientation isEqualToString:@"landscapeLeft"]) { - return UIDeviceOrientationLandscapeLeft; - } else if ([orientation isEqualToString:@"landscapeRight"]) { - return UIDeviceOrientationLandscapeRight; - } else if ([orientation isEqualToString:@"portraitUp"]) { - return UIDeviceOrientationPortrait; - } else { - return UIDeviceOrientationUnknown; - } +UIDeviceOrientation FCPGetUIDeviceOrientationForPigeonDeviceOrientation( + FCPPlatformDeviceOrientation orientation) { + switch (orientation) { + case FCPPlatformDeviceOrientationPortraitDown: + return UIDeviceOrientationPortraitUpsideDown; + case FCPPlatformDeviceOrientationLandscapeLeft: + return UIDeviceOrientationLandscapeLeft; + case FCPPlatformDeviceOrientationLandscapeRight: + return UIDeviceOrientationLandscapeRight; + case FCPPlatformDeviceOrientationPortraitUp: + return UIDeviceOrientationPortrait; + }; } FCPPlatformDeviceOrientation FCPGetPigeonDeviceOrientationForOrientation( @@ -93,49 +47,11 @@ FCPPlatformDeviceOrientation FCPGetPigeonDeviceOrientationForOrientation( }; } -#pragma mark - resolution preset - -FLTResolutionPreset FLTGetFLTResolutionPresetForString(NSString *preset) { - if ([preset isEqualToString:@"veryLow"]) { - return FLTResolutionPresetVeryLow; - } else if ([preset isEqualToString:@"low"]) { - return FLTResolutionPresetLow; - } else if ([preset isEqualToString:@"medium"]) { - return FLTResolutionPresetMedium; - } else if ([preset isEqualToString:@"high"]) { - return FLTResolutionPresetHigh; - } else if ([preset isEqualToString:@"veryHigh"]) { - return FLTResolutionPresetVeryHigh; - } else if ([preset isEqualToString:@"ultraHigh"]) { - return FLTResolutionPresetUltraHigh; - } else if ([preset isEqualToString:@"max"]) { - return FLTResolutionPresetMax; - } else { - return FLTResolutionPresetInvalid; - } -} - -#pragma mark - video format - -OSType FLTGetVideoFormatFromString(NSString *videoFormatString) { - if ([videoFormatString isEqualToString:@"bgra8888"]) { - return kCVPixelFormatType_32BGRA; - } else if ([videoFormatString isEqualToString:@"yuv420"]) { - return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; - } else { - NSLog(@"The selected imageFormatGroup is not supported by iOS. Defaulting to brga8888"); - return kCVPixelFormatType_32BGRA; - } -} - -#pragma mark - file format - -FCPFileFormat FCPGetFileFormatFromString(NSString *fileFormatString) { - if ([fileFormatString isEqualToString:@"jpg"]) { - return FCPFileFormatJPEG; - } else if ([fileFormatString isEqualToString:@"heif"]) { - return FCPFileFormatHEIF; - } else { - return FCPFileFormatInvalid; +OSType FCPGetPixelFormatForPigeonFormat(FCPPlatformImageFormatGroup imageFormat) { + switch (imageFormat) { + case FCPPlatformImageFormatGroupBgra8888: + return kCVPixelFormatType_32BGRA; + case FCPPlatformImageFormatGroupYuv420: + return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; } } diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h index ddc1e25ded1..d8f97926b77 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h @@ -7,9 +7,7 @@ @import Flutter; #import "CameraProperties.h" -#import "FLTCamMediaSettings.h" #import "FLTCamMediaSettingsAVWrapper.h" -#import "FLTThreadSafeTextureRegistry.h" #import "messages.g.h" NS_ASSUME_NONNULL_BEGIN @@ -24,17 +22,17 @@ NS_ASSUME_NONNULL_BEGIN /// The API instance used to communicate with the Dart side of the plugin. Once initially set, this /// should only ever be accessed on the main thread. @property(nonatomic) FCPCameraEventApi *dartAPI; -@property(assign, nonatomic) FLTResolutionPreset resolutionPreset; @property(assign, nonatomic) FCPPlatformExposureMode exposureMode; @property(assign, nonatomic) FCPPlatformFocusMode focusMode; -@property(assign, nonatomic) FLTFlashMode flashMode; +@property(assign, nonatomic) FCPPlatformFlashMode flashMode; // Format used for video and image streaming. @property(assign, nonatomic) FourCharCode videoFormat; -@property(assign, nonatomic) FCPFileFormat fileFormat; +@property(assign, nonatomic) FCPPlatformImageFileFormat fileFormat; +@property(assign, nonatomic) CGFloat minimumAvailableZoomFactor; +@property(assign, nonatomic) CGFloat maximumAvailableZoomFactor; /// Initializes an `FLTCam` instance. /// @param cameraName a name used to uniquely identify the camera. -/// @param resolutionPreset the resolution preset /// @param mediaSettings the media settings configuration parameters /// @param mediaSettingsAVWrapper AVFoundation wrapper to perform media settings related operations /// (for dependency injection in unit tests). @@ -42,8 +40,7 @@ NS_ASSUME_NONNULL_BEGIN /// @param captureSessionQueue the queue on which camera's capture session operations happen. /// @param error report to the caller if any error happened creating the camera. - (instancetype)initWithCameraName:(NSString *)cameraName - resolutionPreset:(NSString *)resolutionPreset - mediaSettings:(FLTCamMediaSettings *)mediaSettings + mediaSettings:(FCPPlatformMediaSettings *)mediaSettings mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper orientation:(UIDeviceOrientation)orientation captureSessionQueue:(dispatch_queue_t)captureSessionQueue @@ -54,26 +51,27 @@ NS_ASSUME_NONNULL_BEGIN - (void)start; - (void)stop; - (void)setDeviceOrientation:(UIDeviceOrientation)orientation; -- (void)captureToFile:(FlutterResult)result; +- (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion; - (void)close; -- (void)startVideoRecordingWithResult:(FlutterResult)result; -- (void)setImageFileFormat:(FCPFileFormat)fileFormat; +- (void)setImageFileFormat:(FCPPlatformImageFileFormat)fileFormat; /// Starts recording a video with an optional streaming messenger. -/// If the messenger is non-null then it will be called for each +/// If the messenger is non-nil then it will be called for each /// captured frame, allowing streaming concurrently with recording. /// /// @param messenger Nullable messenger for capturing each frame. -- (void)startVideoRecordingWithResult:(FlutterResult)result - messengerForStreaming:(nullable NSObject *)messenger; -- (void)stopVideoRecordingWithResult:(FlutterResult)result; -- (void)pauseVideoRecordingWithResult:(FlutterResult)result; -- (void)resumeVideoRecordingWithResult:(FlutterResult)result; -- (void)lockCaptureOrientationWithResult:(FlutterResult)result - orientation:(NSString *)orientationStr; -- (void)unlockCaptureOrientationWithResult:(FlutterResult)result; -- (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr; -- (void)setExposureModeWithResult:(FlutterResult)result mode:(NSString *)modeStr; -- (void)setFocusModeWithResult:(FlutterResult)result mode:(NSString *)modeStr; +- (void)startVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))completion + messengerForStreaming:(nullable NSObject *)messenger; +- (void)stopVideoRecordingWithCompletion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion; +- (void)pauseVideoRecording; +- (void)resumeVideoRecording; +- (void)lockCaptureOrientation:(FCPPlatformDeviceOrientation)orientation; +- (void)unlockCaptureOrientation; +- (void)setFlashMode:(FCPPlatformFlashMode)mode + withCompletion:(void (^)(FlutterError *_Nullable))completion; +- (void)setExposureMode:(FCPPlatformExposureMode)mode; +- (void)setFocusMode:(FCPPlatformFocusMode)mode; - (void)applyFocusMode; /// Acknowledges the receipt of one image stream frame. @@ -95,17 +93,26 @@ NS_ASSUME_NONNULL_BEGIN /// @param focusMode The focus mode that should be applied to the @captureDevice instance. /// @param captureDevice The AVCaptureDevice to which the @focusMode will be applied. - (void)applyFocusMode:(FCPPlatformFocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice; -- (void)pausePreviewWithResult:(FlutterResult)result; -- (void)resumePreviewWithResult:(FlutterResult)result; -- (void)setDescriptionWhileRecording:(NSString *)cameraName result:(FlutterResult)result; -- (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y; -- (void)setFocusPointWithResult:(FlutterResult)result x:(double)x y:(double)y; -- (void)setExposureOffsetWithResult:(FlutterResult)result offset:(double)offset; +- (void)pausePreview; +- (void)resumePreview; +- (void)setDescriptionWhileRecording:(NSString *)cameraName + withCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Sets the exposure point, in a (0,1) coordinate system. +/// +/// If @c point is nil, the exposure point will reset to the center. +- (void)setExposurePoint:(nullable FCPPlatformPoint *)point + withCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Sets the focus point, in a (0,1) coordinate system. +/// +/// If @c point is nil, the focus point will reset to the center. +- (void)setFocusPoint:(nullable FCPPlatformPoint *)point + withCompletion:(void (^)(FlutterError *_Nullable))completion; +- (void)setExposureOffset:(double)offset; - (void)startImageStreamWithMessenger:(NSObject *)messenger; - (void)stopImageStream; -- (void)getMaxZoomLevelWithResult:(FlutterResult)result; -- (void)getMinZoomLevelWithResult:(FlutterResult)result; -- (void)setZoomLevel:(CGFloat)zoom Result:(FlutterResult)result; +- (void)setZoomLevel:(CGFloat)zoom withCompletion:(void (^)(FlutterError *_Nullable))completion; - (void)setUpCaptureSessionForAudio; @end diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index f65af079418..45ab3e08e66 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -51,7 +51,7 @@ @interface FLTCam () @property(readonly, nonatomic) int64_t textureId; -@property(readonly, nonatomic) FLTCamMediaSettings *mediaSettings; +@property(readonly, nonatomic) FCPPlatformMediaSettings *mediaSettings; @property(readonly, nonatomic) FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper; @property(nonatomic) FLTImageStreamHandler *imageStreamHandler; @property(readonly, nonatomic) AVCaptureSession *videoCaptureSession; @@ -114,14 +114,12 @@ @implementation FLTCam NSString *const errorMethod = @"error"; - (instancetype)initWithCameraName:(NSString *)cameraName - resolutionPreset:(NSString *)resolutionPreset - mediaSettings:(FLTCamMediaSettings *)mediaSettings + mediaSettings:(FCPPlatformMediaSettings *)mediaSettings mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper orientation:(UIDeviceOrientation)orientation captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error { return [self initWithCameraName:cameraName - resolutionPreset:resolutionPreset mediaSettings:mediaSettings mediaSettingsAVWrapper:mediaSettingsAVWrapper orientation:orientation @@ -132,16 +130,14 @@ - (instancetype)initWithCameraName:(NSString *)cameraName } - (instancetype)initWithCameraName:(NSString *)cameraName - resolutionPreset:(NSString *)resolutionPreset - mediaSettings:(FLTCamMediaSettings *)mediaSettings + mediaSettings:(FCPPlatformMediaSettings *)mediaSettings mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper orientation:(UIDeviceOrientation)orientation videoCaptureSession:(AVCaptureSession *)videoCaptureSession audioCaptureSession:(AVCaptureSession *)audioCaptureSession captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error { - return [self initWithResolutionPreset:resolutionPreset - mediaSettings:mediaSettings + return [self initWithMediaSettings:mediaSettings mediaSettingsAVWrapper:mediaSettingsAVWrapper orientation:orientation videoCaptureSession:videoCaptureSession @@ -156,30 +152,17 @@ - (instancetype)initWithCameraName:(NSString *)cameraName error:error]; } -- (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset - mediaSettings:(FLTCamMediaSettings *)mediaSettings - mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper - orientation:(UIDeviceOrientation)orientation - videoCaptureSession:(AVCaptureSession *)videoCaptureSession - audioCaptureSession:(AVCaptureSession *)audioCaptureSession - captureSessionQueue:(dispatch_queue_t)captureSessionQueue - captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory - videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat - error:(NSError **)error { +- (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings + mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper + orientation:(UIDeviceOrientation)orientation + videoCaptureSession:(AVCaptureSession *)videoCaptureSession + audioCaptureSession:(AVCaptureSession *)audioCaptureSession + captureSessionQueue:(dispatch_queue_t)captureSessionQueue + captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory + videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat + error:(NSError **)error { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _resolutionPreset = FLTGetFLTResolutionPresetForString(resolutionPreset); - if (_resolutionPreset == FLTResolutionPresetInvalid) { - *error = [NSError - errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : - [NSString stringWithFormat:@"Unknown resolution preset %@", resolutionPreset] - }]; - return nil; - } - _mediaSettings = mediaSettings; _mediaSettingsAVWrapper = mediaSettingsAVWrapper; @@ -192,14 +175,14 @@ - (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset _captureDeviceFactory = captureDeviceFactory; _captureDevice = captureDeviceFactory(); _videoDimensionsForFormat = videoDimensionsForFormat; - _flashMode = _captureDevice.hasFlash ? FLTFlashModeAuto : FLTFlashModeOff; + _flashMode = _captureDevice.hasFlash ? FCPPlatformFlashModeAuto : FCPPlatformFlashModeOff; _exposureMode = FCPPlatformExposureModeAuto; _focusMode = FCPPlatformFocusModeAuto; _lockedCaptureOrientation = UIDeviceOrientationUnknown; _deviceOrientation = orientation; _videoFormat = kCVPixelFormatType_32BGRA; _inProgressSavePhotoDelegates = [NSMutableDictionary dictionary]; - _fileFormat = FCPFileFormatJPEG; + _fileFormat = FCPPlatformImageFileFormatJpeg; // To limit memory consumption, limit the number of frames pending processing. // After some testing, 4 was determined to be the best maximum value. @@ -236,7 +219,7 @@ - (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset // If _resolutionPreset is not supported by camera there is // fallback to lower resolution presets. // If none can be selected there is error condition. - if (![self setCaptureSessionPreset:_resolutionPreset withError:error]) { + if (![self setCaptureSessionPreset:_mediaSettings.resolutionPreset withError:error]) { [_videoCaptureSession commitConfiguration]; [_captureDevice unlockForConfiguration]; return nil; @@ -257,7 +240,7 @@ - (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset } else { // If the frame rate is not important fall to a less restrictive // behavior (no configuration locking). - if (![self setCaptureSessionPreset:_resolutionPreset withError:error]) { + if (![self setCaptureSessionPreset:_mediaSettings.resolutionPreset withError:error]) { return nil; } } @@ -332,7 +315,7 @@ - (void)setVideoFormat:(OSType)videoFormat { @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat)}; } -- (void)setImageFileFormat:(FCPFileFormat)fileFormat { +- (void)setImageFileFormat:(FCPPlatformImageFileFormat)fileFormat { _fileFormat = fileFormat; } @@ -370,10 +353,11 @@ - (void)updateOrientation:(UIDeviceOrientation)orientation } } -- (void)captureToFile:(FlutterResult)result { +- (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion { AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; - if (_resolutionPreset == FLTResolutionPresetMax) { + if (self.mediaSettings.resolutionPreset == FCPPlatformResolutionPresetMax) { [settings setHighResolutionPhotoEnabled:YES]; } @@ -382,7 +366,7 @@ - (void)captureToFile:(FlutterResult)result { BOOL isHEVCCodecAvailable = [self.capturePhotoOutput.availablePhotoCodecTypes containsObject:AVVideoCodecTypeHEVC]; - if (_fileFormat == FCPFileFormatHEIF && isHEVCCodecAvailable) { + if (_fileFormat == FCPPlatformImageFileFormatHeif && isHEVCCodecAvailable) { settings = [AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey : AVVideoCodecTypeHEVC}]; extension = @"heif"; @@ -390,7 +374,7 @@ - (void)captureToFile:(FlutterResult)result { extension = @"jpg"; } - AVCaptureFlashMode avFlashMode = FLTGetAVCaptureFlashModeForFLTFlashMode(_flashMode); + AVCaptureFlashMode avFlashMode = FCPGetAVCaptureFlashModeForPigeonFlashMode(_flashMode); if (avFlashMode != -1) { [settings setFlashMode:avFlashMode]; } @@ -400,7 +384,7 @@ - (void)captureToFile:(FlutterResult)result { prefix:@"CAP_" error:error]; if (error) { - result(FlutterErrorFromNSError(error)); + completion(nil, FlutterErrorFromNSError(error)); return; } @@ -419,10 +403,10 @@ - (void)captureToFile:(FlutterResult)result { }); if (error) { - result(FlutterErrorFromNSError(error)); + completion(nil, FlutterErrorFromNSError(error)); } else { NSAssert(path, @"Path must not be nil if no error."); - result(path); + completion(path, nil); } }]; @@ -477,9 +461,10 @@ - (NSString *)getTemporaryFilePathWithExtension:(NSString *)extension return file; } -- (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError:(NSError **)error { +- (BOOL)setCaptureSessionPreset:(FCPPlatformResolutionPreset)resolutionPreset + withError:(NSError **)error { switch (resolutionPreset) { - case FLTResolutionPresetMax: { + case FCPPlatformResolutionPresetMax: { AVCaptureDeviceFormat *bestFormat = [self highestResolutionFormatForCaptureDevice:_captureDevice]; if (bestFormat) { @@ -497,7 +482,7 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: } } } - case FLTResolutionPresetUltraHigh: + case FCPPlatformResolutionPresetUltraHigh: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; _previewSize = CGSizeMake(3840, 2160); @@ -510,25 +495,25 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: _captureDevice.activeFormat.highResolutionStillImageDimensions.height); break; } - case FLTResolutionPresetVeryHigh: + case FCPPlatformResolutionPresetVeryHigh: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset1920x1080; _previewSize = CGSizeMake(1920, 1080); break; } - case FLTResolutionPresetHigh: + case FCPPlatformResolutionPresetHigh: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset1280x720; _previewSize = CGSizeMake(1280, 720); break; } - case FLTResolutionPresetMedium: + case FCPPlatformResolutionPresetMedium: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset640x480; _previewSize = CGSizeMake(640, 480); break; } - case FLTResolutionPresetLow: + case FCPPlatformResolutionPresetLow: if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset352x288]) { _videoCaptureSession.sessionPreset = AVCaptureSessionPreset352x288; _previewSize = CGSizeMake(352, 288); @@ -819,12 +804,8 @@ - (CVPixelBufferRef)copyPixelBuffer { return pixelBuffer; } -- (void)startVideoRecordingWithResult:(FlutterResult)result { - [self startVideoRecordingWithResult:result messengerForStreaming:nil]; -} - -- (void)startVideoRecordingWithResult:(FlutterResult)result - messengerForStreaming:(nullable NSObject *)messenger { +- (void)startVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))completion + messengerForStreaming:(nullable NSObject *)messenger { if (!_isRecording) { if (messenger != nil) { [self startImageStreamWithMessenger:messenger]; @@ -836,11 +817,13 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result prefix:@"REC_" error:error]; if (error) { - result(FlutterErrorFromNSError(error)); + completion(FlutterErrorFromNSError(error)); return; } if (![self setupWriterForPath:_videoRecordingPath]) { - result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); + completion([FlutterError errorWithCode:@"IOError" + message:@"Setup Writer Failed" + details:nil]); return; } _isRecording = YES; @@ -849,13 +832,16 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result _audioTimeOffset = CMTimeMake(0, 1); _videoIsDisconnected = NO; _audioIsDisconnected = NO; - result(nil); + completion(nil); } else { - result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); + completion([FlutterError errorWithCode:@"Error" + message:@"Video is already recording" + details:nil]); } } -- (void)stopVideoRecordingWithResult:(FlutterResult)result { +- (void)stopVideoRecordingWithCompletion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion { if (_isRecording) { _isRecording = NO; @@ -863,12 +849,12 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { [_videoWriter finishWritingWithCompletionHandler:^{ if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { [self updateOrientation]; - result(self->_videoRecordingPath); + completion(self->_videoRecordingPath, nil); self->_videoRecordingPath = nil; } else { - result([FlutterError errorWithCode:@"IOError" - message:@"AVAssetWriter could not finish writing!" - details:nil]); + completion(nil, [FlutterError errorWithCode:@"IOError" + message:@"AVAssetWriter could not finish writing!" + details:nil]); } }]; } @@ -877,75 +863,47 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorResourceUnavailable userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; - result(FlutterErrorFromNSError(error)); + completion(nil, FlutterErrorFromNSError(error)); } } -- (void)pauseVideoRecordingWithResult:(FlutterResult)result { +- (void)pauseVideoRecording { _isRecordingPaused = YES; _videoIsDisconnected = YES; _audioIsDisconnected = YES; - result(nil); } -- (void)resumeVideoRecordingWithResult:(FlutterResult)result { +- (void)resumeVideoRecording { _isRecordingPaused = NO; - result(nil); } -- (void)lockCaptureOrientationWithResult:(FlutterResult)result - orientation:(NSString *)orientationStr { - UIDeviceOrientation orientation = FLTGetUIDeviceOrientationForString(orientationStr); - // "Unknown" should never be sent, so is used to represent an unexpected - // value. - if (orientation == UIDeviceOrientationUnknown) { - result(FlutterErrorFromNSError([NSError - errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : - [NSString stringWithFormat:@"Unknown device orientation %@", orientationStr] - }])); - return; - } - +- (void)lockCaptureOrientation:(FCPPlatformDeviceOrientation)pigeonOrientation { + UIDeviceOrientation orientation = + FCPGetUIDeviceOrientationForPigeonDeviceOrientation(pigeonOrientation); if (_lockedCaptureOrientation != orientation) { _lockedCaptureOrientation = orientation; [self updateOrientation]; } - - result(nil); } -- (void)unlockCaptureOrientationWithResult:(FlutterResult)result { +- (void)unlockCaptureOrientation { _lockedCaptureOrientation = UIDeviceOrientationUnknown; [self updateOrientation]; - result(nil); } -- (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { - FLTFlashMode mode = FLTGetFLTFlashModeForString(modeStr); - if (mode == FLTFlashModeInvalid) { - result(FlutterErrorFromNSError([NSError - errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : - [NSString stringWithFormat:@"Unknown flash mode %@", modeStr] - }])); - return; - } - if (mode == FLTFlashModeTorch) { +- (void)setFlashMode:(FCPPlatformFlashMode)mode + withCompletion:(void (^)(FlutterError *_Nullable))completion { + if (mode == FCPPlatformFlashModeTorch) { if (!_captureDevice.hasTorch) { - result([FlutterError errorWithCode:@"setFlashModeFailed" - message:@"Device does not support torch mode" - details:nil]); + completion([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not support torch mode" + details:nil]); return; } if (!_captureDevice.isTorchAvailable) { - result([FlutterError errorWithCode:@"setFlashModeFailed" - message:@"Torch mode is currently not available" - details:nil]); + completion([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Torch mode is currently not available" + details:nil]); return; } if (_captureDevice.torchMode != AVCaptureTorchModeOn) { @@ -955,17 +913,17 @@ - (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { } } else { if (!_captureDevice.hasFlash) { - result([FlutterError errorWithCode:@"setFlashModeFailed" - message:@"Device does not have flash capabilities" - details:nil]); + completion([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not have flash capabilities" + details:nil]); return; } - AVCaptureFlashMode avFlashMode = FLTGetAVCaptureFlashModeForFLTFlashMode(mode); + AVCaptureFlashMode avFlashMode = FCPGetAVCaptureFlashModeForPigeonFlashMode(mode); if (![_capturePhotoOutput.supportedFlashModes containsObject:[NSNumber numberWithInt:((int)avFlashMode)]]) { - result([FlutterError errorWithCode:@"setFlashModeFailed" - message:@"Device does not support this specific flash mode" - details:nil]); + completion([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not support this specific flash mode" + details:nil]); return; } if (_captureDevice.torchMode != AVCaptureTorchModeOff) { @@ -975,14 +933,12 @@ - (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { } } _flashMode = mode; - result(nil); + completion(nil); } -- (void)setExposureModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { - FCPPlatformExposureMode mode = FCPGetExposureModeForString(modeStr); +- (void)setExposureMode:(FCPPlatformExposureMode)mode { _exposureMode = mode; [self applyExposureMode]; - result(nil); } - (void)applyExposureMode { @@ -1002,11 +958,9 @@ - (void)applyExposureMode { [_captureDevice unlockForConfiguration]; } -- (void)setFocusModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { - FCPPlatformFocusMode mode = FCPGetFocusModeForString(modeStr); +- (void)setFocusMode:(FCPPlatformFocusMode)mode { _focusMode = mode; [self applyFocusMode]; - result(nil); } - (void)applyFocusMode { @@ -1032,21 +986,20 @@ - (void)applyFocusMode:(FCPPlatformFocusMode)focusMode onDevice:(AVCaptureDevice [captureDevice unlockForConfiguration]; } -- (void)pausePreviewWithResult:(FlutterResult)result { +- (void)pausePreview { _isPreviewPaused = true; - result(nil); } -- (void)resumePreviewWithResult:(FlutterResult)result { +- (void)resumePreview { _isPreviewPaused = false; - result(nil); } -- (void)setDescriptionWhileRecording:(NSString *)cameraName result:(FlutterResult)result { +- (void)setDescriptionWhileRecording:(NSString *)cameraName + withCompletion:(void (^)(FlutterError *_Nullable))completion { if (!_isRecording) { - result([FlutterError errorWithCode:@"setDescriptionWhileRecordingFailed" - message:@"Device was not recording" - details:nil]); + completion([FlutterError errorWithCode:@"setDescriptionWhileRecordingFailed" + message:@"Device was not recording" + details:nil]); return; } @@ -1066,7 +1019,7 @@ - (void)setDescriptionWhileRecording:(NSString *)cameraName result:(FlutterResul NSError *error = nil; AVCaptureConnection *newConnection = [self createConnection:&error]; if (error) { - result(FlutterErrorFromNSError(error)); + completion(FlutterErrorFromNSError(error)); return; } @@ -1077,41 +1030,41 @@ - (void)setDescriptionWhileRecording:(NSString *)cameraName result:(FlutterResul // Add the new connections to the session. if (![_videoCaptureSession canAddInput:_captureVideoInput]) - result([FlutterError errorWithCode:@"VideoError" - message:@"Unable switch video input" - details:nil]); + completion([FlutterError errorWithCode:@"VideoError" + message:@"Unable switch video input" + details:nil]); [_videoCaptureSession addInputWithNoConnections:_captureVideoInput]; if (![_videoCaptureSession canAddOutput:_captureVideoOutput]) - result([FlutterError errorWithCode:@"VideoError" - message:@"Unable switch video output" - details:nil]); + completion([FlutterError errorWithCode:@"VideoError" + message:@"Unable switch video output" + details:nil]); [_videoCaptureSession addOutputWithNoConnections:_captureVideoOutput]; if (![_videoCaptureSession canAddConnection:newConnection]) - result([FlutterError errorWithCode:@"VideoError" - message:@"Unable switch video connection" - details:nil]); + completion([FlutterError errorWithCode:@"VideoError" + message:@"Unable switch video connection" + details:nil]); [_videoCaptureSession addConnection:newConnection]; [_videoCaptureSession commitConfiguration]; - result(nil); + completion(nil); } -- (CGPoint)getCGPointForCoordsWithOrientation:(UIDeviceOrientation)orientation - x:(double)x - y:(double)y { - double oldX = x, oldY = y; +- (CGPoint)CGPointForPoint:(nonnull FCPPlatformPoint *)point + withOrientation:(UIDeviceOrientation)orientation { + double x = point.x; + double y = point.y; switch (orientation) { case UIDeviceOrientationPortrait: // 90 ccw - y = 1 - oldX; - x = oldY; + y = 1 - point.x; + x = point.y; break; case UIDeviceOrientationPortraitUpsideDown: // 90 cw - x = 1 - oldY; - y = oldX; + x = 1 - point.y; + y = point.x; break; case UIDeviceOrientationLandscapeRight: // 180 - x = 1 - x; - y = 1 - y; + x = 1 - point.x; + y = 1 - point.y; break; case UIDeviceOrientationLandscapeLeft: default: @@ -1121,48 +1074,53 @@ - (CGPoint)getCGPointForCoordsWithOrientation:(UIDeviceOrientation)orientation return CGPointMake(x, y); } -- (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y { +- (void)setExposurePoint:(FCPPlatformPoint *)point + withCompletion:(void (^)(FlutterError *_Nullable))completion { if (!_captureDevice.isExposurePointOfInterestSupported) { - result([FlutterError errorWithCode:@"setExposurePointFailed" - message:@"Device does not have exposure point capabilities" - details:nil]); + completion([FlutterError errorWithCode:@"setExposurePointFailed" + message:@"Device does not have exposure point capabilities" + details:nil]); return; } UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; [_captureDevice lockForConfiguration:nil]; - [_captureDevice setExposurePointOfInterest:[self getCGPointForCoordsWithOrientation:orientation - x:x - y:y]]; + // A nil point resets to the center. + [_captureDevice + setExposurePointOfInterest:[self CGPointForPoint:(point + ?: [FCPPlatformPoint makeWithX:0.5 + y:0.5]) + withOrientation:orientation]]; [_captureDevice unlockForConfiguration]; // Retrigger auto exposure [self applyExposureMode]; - result(nil); + completion(nil); } -- (void)setFocusPointWithResult:(FlutterResult)result x:(double)x y:(double)y { +- (void)setFocusPoint:(FCPPlatformPoint *)point + withCompletion:(void (^)(FlutterError *_Nullable))completion { if (!_captureDevice.isFocusPointOfInterestSupported) { - result([FlutterError errorWithCode:@"setFocusPointFailed" - message:@"Device does not have focus point capabilities" - details:nil]); + completion([FlutterError errorWithCode:@"setFocusPointFailed" + message:@"Device does not have focus point capabilities" + details:nil]); return; } UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; [_captureDevice lockForConfiguration:nil]; - - [_captureDevice setFocusPointOfInterest:[self getCGPointForCoordsWithOrientation:orientation - x:x - y:y]]; + // A nil point resets to the center. + [_captureDevice + setFocusPointOfInterest:[self + CGPointForPoint:(point ?: [FCPPlatformPoint makeWithX:0.5 y:0.5]) + withOrientation:orientation]]; [_captureDevice unlockForConfiguration]; // Retrigger auto focus [self applyFocusMode]; - result(nil); + completion(nil); } -- (void)setExposureOffsetWithResult:(FlutterResult)result offset:(double)offset { +- (void)setExposureOffset:(double)offset { [_captureDevice lockForConfiguration:nil]; [_captureDevice setExposureTargetBias:offset completionHandler:nil]; [_captureDevice unlockForConfiguration]; - result(@(offset)); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { @@ -1214,46 +1172,34 @@ - (void)receivedImageStreamData { self.streamingPendingFramesCount--; } -- (void)getMaxZoomLevelWithResult:(FlutterResult)result { - CGFloat maxZoomFactor = [self getMaxAvailableZoomFactor]; - - result([NSNumber numberWithFloat:maxZoomFactor]); -} - -- (void)getMinZoomLevelWithResult:(FlutterResult)result { - CGFloat minZoomFactor = [self getMinAvailableZoomFactor]; - result([NSNumber numberWithFloat:minZoomFactor]); -} - -- (void)setZoomLevel:(CGFloat)zoom Result:(FlutterResult)result { - CGFloat maxAvailableZoomFactor = [self getMaxAvailableZoomFactor]; - CGFloat minAvailableZoomFactor = [self getMinAvailableZoomFactor]; - - if (maxAvailableZoomFactor < zoom || minAvailableZoomFactor > zoom) { +- (void)setZoomLevel:(CGFloat)zoom withCompletion:(void (^)(FlutterError *_Nullable))completion { + if (_captureDevice.maxAvailableVideoZoomFactor < zoom || + _captureDevice.minAvailableVideoZoomFactor > zoom) { NSString *errorMessage = [NSString stringWithFormat:@"Zoom level out of bounds (zoom level should be between %f and %f).", - minAvailableZoomFactor, maxAvailableZoomFactor]; + _captureDevice.minAvailableVideoZoomFactor, + _captureDevice.maxAvailableVideoZoomFactor]; - result([FlutterError errorWithCode:@"ZOOM_ERROR" message:errorMessage details:nil]); + completion([FlutterError errorWithCode:@"ZOOM_ERROR" message:errorMessage details:nil]); return; } NSError *error = nil; if (![_captureDevice lockForConfiguration:&error]) { - result(FlutterErrorFromNSError(error)); + completion(FlutterErrorFromNSError(error)); return; } _captureDevice.videoZoomFactor = zoom; [_captureDevice unlockForConfiguration]; - result(nil); + completion(nil); } -- (CGFloat)getMinAvailableZoomFactor { +- (CGFloat)minimumAvailableZoomFactor { return _captureDevice.minAvailableVideoZoomFactor; } -- (CGFloat)getMaxAvailableZoomFactor { +- (CGFloat)maximumAvailableZoomFactor { return _captureDevice.maxAvailableVideoZoomFactor; } @@ -1335,7 +1281,7 @@ - (BOOL)setupWriterForPath:(NSString *)path { [_audioOutput setSampleBufferDelegate:self queue:_captureSessionQueue]; } - if (_flashMode == FLTFlashModeTorch) { + if (_flashMode == FCPPlatformFlashModeTorch) { [self.captureDevice lockForConfiguration:nil]; [self.captureDevice setTorchMode:AVCaptureTorchModeOn]; [self.captureDevice unlockForConfiguration]; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCamMediaSettings.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCamMediaSettings.h deleted file mode 100644 index 004accfceb7..00000000000 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCamMediaSettings.h +++ /dev/null @@ -1,54 +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 Foundation; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Media settings configuration parameters. - */ -@interface FLTCamMediaSettings : NSObject - -/** - * @property framesPerSecond optional frame rate of video being recorded. - */ -@property(atomic, readonly, strong, nullable) NSNumber *framesPerSecond; - -/** - * @property videoBitrate optional bitrate of video being recorded. - */ -@property(atomic, readonly, strong, nullable) NSNumber *videoBitrate; - -/** - * @property audioBitrate optional bitrate of audio being recorded. - */ -@property(atomic, readonly, strong, nullable) NSNumber *audioBitrate; - -/** - * @property enableAudio whether audio should be recorded. - */ -@property(atomic, readonly) BOOL enableAudio; - -/** - * @method initWithFramesPerSecond:videoBitrate:audioBitrate:enableAudio: - * - * @abstract Initialize `FLTCamMediaSettings`. - * - * @param framesPerSecond optional frame rate of video being recorded. - * @param videoBitrate optional bitrate of video being recorded. - * @param audioBitrate optional bitrate of audio being recorded. - * @param enableAudio whether audio should be recorded. - * - * @result FLTCamMediaSettings instance - */ -- (instancetype)initWithFramesPerSecond:(nullable NSNumber *)framesPerSecond - videoBitrate:(nullable NSNumber *)videoBitrate - audioBitrate:(nullable NSNumber *)audioBitrate - enableAudio:(BOOL)enableAudio NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCamMediaSettings.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCamMediaSettings.m deleted file mode 100644 index 5c2ca5ae995..00000000000 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCamMediaSettings.m +++ /dev/null @@ -1,36 +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 "FLTCamMediaSettings.h" - -static void AssertPositiveNumberOrNil(NSNumber *_Nullable param, const char *_Nonnull paramName) { - if (param != nil) { - NSCAssert(!isnan([param doubleValue]), @"%s is NaN", paramName); - NSCAssert([param doubleValue] > 0, @"%s is not positive: %@", paramName, param); - } -} - -@implementation FLTCamMediaSettings - -- (instancetype)initWithFramesPerSecond:(nullable NSNumber *)framesPerSecond - videoBitrate:(nullable NSNumber *)videoBitrate - audioBitrate:(nullable NSNumber *)audioBitrate - enableAudio:(BOOL)enableAudio { - self = [super init]; - - if (self != nil) { - AssertPositiveNumberOrNil(framesPerSecond, "framesPerSecond"); - AssertPositiveNumberOrNil(videoBitrate, "videoBitrate"); - AssertPositiveNumberOrNil(audioBitrate, "audioBitrate"); - - _framesPerSecond = framesPerSecond; - _videoBitrate = videoBitrate; - _audioBitrate = audioBitrate; - _enableAudio = enableAudio; - } - - return self; -} - -@end diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h index ed9fad64d3e..d05838f49a7 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h @@ -55,8 +55,7 @@ typedef AVCaptureDevice * (^CaptureDeviceFactory)(void); /// Initializes a camera instance. /// Allows for injecting dependencies that are usually internal. - (instancetype)initWithCameraName:(NSString *)cameraName - resolutionPreset:(NSString *)resolutionPreset - mediaSettings:(FLTCamMediaSettings *)mediaSettings + mediaSettings:(FCPPlatformMediaSettings *)mediaSettings mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper orientation:(UIDeviceOrientation)orientation videoCaptureSession:(AVCaptureSession *)videoCaptureSession @@ -67,16 +66,15 @@ typedef AVCaptureDevice * (^CaptureDeviceFactory)(void); /// Initializes a camera instance. /// Allows for testing with specified resolution, audio preference, orientation, /// and direct access to capture sessions and blocks. -- (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset - mediaSettings:(FLTCamMediaSettings *)mediaSettings - mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper - orientation:(UIDeviceOrientation)orientation - videoCaptureSession:(AVCaptureSession *)videoCaptureSession - audioCaptureSession:(AVCaptureSession *)audioCaptureSession - captureSessionQueue:(dispatch_queue_t)captureSessionQueue - captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory - videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat - error:(NSError **)error; +- (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings + mediaSettingsAVWrapper:(FLTCamMediaSettingsAVWrapper *)mediaSettingsAVWrapper + orientation:(UIDeviceOrientation)orientation + videoCaptureSession:(AVCaptureSession *)videoCaptureSession + audioCaptureSession:(AVCaptureSession *)audioCaptureSession + captureSessionQueue:(dispatch_queue_t)captureSessionQueue + captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory + videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat + error:(NSError **)error; /// Start streaming images. - (void)startImageStreamWithMessenger:(NSObject *)messenger diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h deleted file mode 100644 index 2f80f684e42..00000000000 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h +++ /dev/null @@ -1,36 +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 - -/// A thread safe wrapper for FlutterTextureRegistry that can be called from any thread, by -/// dispatching its underlying engine calls to the main thread. -@interface FLTThreadSafeTextureRegistry : NSObject - -/// Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to -/// FlutterTextureRegistry. -/// @param registry The FlutterTextureRegistry object to be wrapped. -- (instancetype)initWithTextureRegistry:(NSObject *)registry; - -/// Registers a `FlutterTexture` on the main thread for usage in Flutter and returns an id that can -/// be used to reference that texture when calling into Flutter with channels. -/// -/// On success the completion block completes with the pointer to the registered texture, else with -/// 0. The completion block runs on the main thread. -- (void)registerTexture:(NSObject *)texture - completion:(void (^)(int64_t))completion; - -/// Notifies the Flutter engine on the main thread that the given texture has been updated. -- (void)textureFrameAvailable:(int64_t)textureId; - -/// Notifies the Flutter engine on the main thread to unregister a `FlutterTexture` that has been -/// previously registered with `registerTexture:`. -/// @param textureId The result that was previously returned from `registerTexture:`. -- (void)unregisterTexture:(int64_t)textureId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.m b/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.m deleted file mode 100644 index b82d566d740..00000000000 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.m +++ /dev/null @@ -1,46 +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 "FLTThreadSafeTextureRegistry.h" -#import "QueueUtils.h" - -@interface FLTThreadSafeTextureRegistry () -@property(nonatomic, strong) NSObject *registry; -@end - -@implementation FLTThreadSafeTextureRegistry - -- (instancetype)initWithTextureRegistry:(NSObject *)registry { - self = [super init]; - if (self) { - _registry = registry; - } - return self; -} - -- (void)registerTexture:(NSObject *)texture - completion:(void (^)(int64_t))completion { - __weak typeof(self) weakSelf = self; - FLTEnsureToRunOnMainQueue(^{ - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - completion([strongSelf.registry registerTexture:texture]); - }); -} - -- (void)textureFrameAvailable:(int64_t)textureId { - __weak typeof(self) weakSelf = self; - FLTEnsureToRunOnMainQueue(^{ - [weakSelf.registry textureFrameAvailable:textureId]; - }); -} - -- (void)unregisterTexture:(int64_t)textureId { - __weak typeof(self) weakSelf = self; - FLTEnsureToRunOnMainQueue(^{ - [weakSelf.registry unregisterTexture:textureId]; - }); -} - -@end diff --git a/packages/camera/camera_avfoundation/ios/Classes/messages.g.h b/packages/camera/camera_avfoundation/ios/Classes/messages.g.h index 4f17971bf7c..8e3dd431443 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/messages.g.h +++ b/packages/camera/camera_avfoundation/ios/Classes/messages.g.h @@ -52,6 +52,19 @@ typedef NS_ENUM(NSUInteger, FCPPlatformExposureMode) { - (instancetype)initWithValue:(FCPPlatformExposureMode)value; @end +typedef NS_ENUM(NSUInteger, FCPPlatformFlashMode) { + FCPPlatformFlashModeOff = 0, + FCPPlatformFlashModeAuto = 1, + FCPPlatformFlashModeAlways = 2, + FCPPlatformFlashModeTorch = 3, +}; + +/// Wrapper for FCPPlatformFlashMode to allow for nullability. +@interface FCPPlatformFlashModeBox : NSObject +@property(nonatomic, assign) FCPPlatformFlashMode value; +- (instancetype)initWithValue:(FCPPlatformFlashMode)value; +@end + typedef NS_ENUM(NSUInteger, FCPPlatformFocusMode) { FCPPlatformFocusModeAuto = 0, FCPPlatformFocusModeLocked = 1, @@ -63,8 +76,48 @@ typedef NS_ENUM(NSUInteger, FCPPlatformFocusMode) { - (instancetype)initWithValue:(FCPPlatformFocusMode)value; @end +/// Pigeon version of ImageFileFormat. +typedef NS_ENUM(NSUInteger, FCPPlatformImageFileFormat) { + FCPPlatformImageFileFormatJpeg = 0, + FCPPlatformImageFileFormatHeif = 1, +}; + +/// Wrapper for FCPPlatformImageFileFormat to allow for nullability. +@interface FCPPlatformImageFileFormatBox : NSObject +@property(nonatomic, assign) FCPPlatformImageFileFormat value; +- (instancetype)initWithValue:(FCPPlatformImageFileFormat)value; +@end + +typedef NS_ENUM(NSUInteger, FCPPlatformImageFormatGroup) { + FCPPlatformImageFormatGroupBgra8888 = 0, + FCPPlatformImageFormatGroupYuv420 = 1, +}; + +/// Wrapper for FCPPlatformImageFormatGroup to allow for nullability. +@interface FCPPlatformImageFormatGroupBox : NSObject +@property(nonatomic, assign) FCPPlatformImageFormatGroup value; +- (instancetype)initWithValue:(FCPPlatformImageFormatGroup)value; +@end + +typedef NS_ENUM(NSUInteger, FCPPlatformResolutionPreset) { + FCPPlatformResolutionPresetLow = 0, + FCPPlatformResolutionPresetMedium = 1, + FCPPlatformResolutionPresetHigh = 2, + FCPPlatformResolutionPresetVeryHigh = 3, + FCPPlatformResolutionPresetUltraHigh = 4, + FCPPlatformResolutionPresetMax = 5, +}; + +/// Wrapper for FCPPlatformResolutionPreset to allow for nullability. +@interface FCPPlatformResolutionPresetBox : NSObject +@property(nonatomic, assign) FCPPlatformResolutionPreset value; +- (instancetype)initWithValue:(FCPPlatformResolutionPreset)value; +@end + @class FCPPlatformCameraDescription; @class FCPPlatformCameraState; +@class FCPPlatformMediaSettings; +@class FCPPlatformPoint; @class FCPPlatformSize; @interface FCPPlatformCameraDescription : NSObject @@ -98,6 +151,29 @@ typedef NS_ENUM(NSUInteger, FCPPlatformFocusMode) { @property(nonatomic, assign) BOOL focusPointSupported; @end +@interface FCPPlatformMediaSettings : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithResolutionPreset:(FCPPlatformResolutionPreset)resolutionPreset + framesPerSecond:(nullable NSNumber *)framesPerSecond + videoBitrate:(nullable NSNumber *)videoBitrate + audioBitrate:(nullable NSNumber *)audioBitrate + enableAudio:(BOOL)enableAudio; +@property(nonatomic, assign) FCPPlatformResolutionPreset resolutionPreset; +@property(nonatomic, strong, nullable) NSNumber *framesPerSecond; +@property(nonatomic, strong, nullable) NSNumber *videoBitrate; +@property(nonatomic, strong, nullable) NSNumber *audioBitrate; +@property(nonatomic, assign) BOOL enableAudio; +@end + +@interface FCPPlatformPoint : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithX:(double)x y:(double)y; +@property(nonatomic, assign) double x; +@property(nonatomic, assign) double y; +@end + @interface FCPPlatformSize : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; @@ -113,6 +189,92 @@ NSObject *FCPCameraApiGetCodec(void); /// Returns the list of available cameras. - (void)availableCamerasWithCompletion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; +/// Create a new camera with the given settings, and returns its ID. +- (void)createCameraWithName:(NSString *)cameraName + settings:(FCPPlatformMediaSettings *)settings + completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +/// Initializes the camera with the given ID. +- (void)initializeCamera:(NSInteger)cameraId + withImageFormat:(FCPPlatformImageFormatGroup)imageFormat + completion:(void (^)(FlutterError *_Nullable))completion; +/// Begins streaming frames from the camera. +- (void)startImageStreamWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Stops streaming frames from the camera. +- (void)stopImageStreamWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Called by the Dart side of the plugin when it has received the last image +/// frame sent. +/// +/// This is used to throttle sending frames across the channel. +- (void)receivedImageStreamDataWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Indicates that the given camera is no longer being used on the Dart side, +/// and any associated resources can be cleaned up. +- (void)disposeCamera:(NSInteger)cameraId completion:(void (^)(FlutterError *_Nullable))completion; +/// Locks the camera capture to the current device orientation. +- (void)lockCaptureOrientation:(FCPPlatformDeviceOrientation)orientation + completion:(void (^)(FlutterError *_Nullable))completion; +/// Unlocks camera capture orientation, allowing it to automatically adapt to +/// device orientation. +- (void)unlockCaptureOrientationWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Takes a picture with the current settings, and returns the path to the +/// resulting file. +- (void)takePictureWithCompletion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion; +/// Does any preprocessing necessary before beginning to record video. +- (void)prepareForVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Begins recording video, optionally enabling streaming to Dart at the same +/// time. +- (void)startVideoRecordingWithStreaming:(BOOL)enableStream + completion:(void (^)(FlutterError *_Nullable))completion; +/// Stops recording video, and results the path to the resulting file. +- (void)stopVideoRecordingWithCompletion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion; +/// Pauses video recording. +- (void)pauseVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Resumes a previously paused video recording. +- (void)resumeVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Switches the camera to the given flash mode. +- (void)setFlashMode:(FCPPlatformFlashMode)mode + completion:(void (^)(FlutterError *_Nullable))completion; +/// Switches the camera to the given exposure mode. +- (void)setExposureMode:(FCPPlatformExposureMode)mode + completion:(void (^)(FlutterError *_Nullable))completion; +/// Anchors auto-exposure to the given point in (0,1) coordinate space. +/// +/// A null value resets to the default exposure point. +- (void)setExposurePoint:(nullable FCPPlatformPoint *)point + completion:(void (^)(FlutterError *_Nullable))completion; +/// Returns the minimum exposure offset supported by the camera. +- (void)getMinimumExposureOffset:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +/// Returns the maximum exposure offset supported by the camera. +- (void)getMaximumExposureOffset:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +/// Sets the exposure offset manually to the given value. +- (void)setExposureOffset:(double)offset completion:(void (^)(FlutterError *_Nullable))completion; +/// Switches the camera to the given focus mode. +- (void)setFocusMode:(FCPPlatformFocusMode)mode + completion:(void (^)(FlutterError *_Nullable))completion; +/// Anchors auto-focus to the given point in (0,1) coordinate space. +/// +/// A null value resets to the default focus point. +- (void)setFocusPoint:(nullable FCPPlatformPoint *)point + completion:(void (^)(FlutterError *_Nullable))completion; +/// Returns the minimum zoom level supported by the camera. +- (void)getMinimumZoomLevel:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +/// Returns the maximum zoom level supported by the camera. +- (void)getMaximumZoomLevel:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +/// Sets the zoom factor. +- (void)setZoomLevel:(double)zoom completion:(void (^)(FlutterError *_Nullable))completion; +/// Pauses streaming of preview frames. +- (void)pausePreviewWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Resumes a previously paused preview stream. +- (void)resumePreviewWithCompletion:(void (^)(FlutterError *_Nullable))completion; +/// Changes the camera used while recording video. +/// +/// This should only be called while video recording is active. +- (void)updateDescriptionWhileRecordingCameraName:(NSString *)cameraName + completion:(void (^)(FlutterError *_Nullable))completion; +/// Sets the file format used for taking pictures. +- (void)setImageFileFormat:(FCPPlatformImageFileFormat)format + completion:(void (^)(FlutterError *_Nullable))completion; @end extern void SetUpFCPCameraApi(id binaryMessenger, diff --git a/packages/camera/camera_avfoundation/ios/Classes/messages.g.m b/packages/camera/camera_avfoundation/ios/Classes/messages.g.m index fd1100c0b77..d90b63d7a01 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/messages.g.m +++ b/packages/camera/camera_avfoundation/ios/Classes/messages.g.m @@ -69,6 +69,16 @@ - (instancetype)initWithValue:(FCPPlatformExposureMode)value { } @end +@implementation FCPPlatformFlashModeBox +- (instancetype)initWithValue:(FCPPlatformFlashMode)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + @implementation FCPPlatformFocusModeBox - (instancetype)initWithValue:(FCPPlatformFocusMode)value { self = [super init]; @@ -79,6 +89,37 @@ - (instancetype)initWithValue:(FCPPlatformFocusMode)value { } @end +/// Pigeon version of ImageFileFormat. +@implementation FCPPlatformImageFileFormatBox +- (instancetype)initWithValue:(FCPPlatformImageFileFormat)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +@implementation FCPPlatformImageFormatGroupBox +- (instancetype)initWithValue:(FCPPlatformImageFormatGroup)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +@implementation FCPPlatformResolutionPresetBox +- (instancetype)initWithValue:(FCPPlatformResolutionPreset)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + @interface FCPPlatformCameraDescription () + (FCPPlatformCameraDescription *)fromList:(NSArray *)list; + (nullable FCPPlatformCameraDescription *)nullableFromList:(NSArray *)list; @@ -91,6 +132,18 @@ + (nullable FCPPlatformCameraState *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface FCPPlatformMediaSettings () ++ (FCPPlatformMediaSettings *)fromList:(NSArray *)list; ++ (nullable FCPPlatformMediaSettings *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface FCPPlatformPoint () ++ (FCPPlatformPoint *)fromList:(NSArray *)list; ++ (nullable FCPPlatformPoint *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface FCPPlatformSize () + (FCPPlatformSize *)fromList:(NSArray *)list; + (nullable FCPPlatformSize *)nullableFromList:(NSArray *)list; @@ -159,6 +212,67 @@ - (NSArray *)toList { } @end +@implementation FCPPlatformMediaSettings ++ (instancetype)makeWithResolutionPreset:(FCPPlatformResolutionPreset)resolutionPreset + framesPerSecond:(nullable NSNumber *)framesPerSecond + videoBitrate:(nullable NSNumber *)videoBitrate + audioBitrate:(nullable NSNumber *)audioBitrate + enableAudio:(BOOL)enableAudio { + FCPPlatformMediaSettings *pigeonResult = [[FCPPlatformMediaSettings alloc] init]; + pigeonResult.resolutionPreset = resolutionPreset; + pigeonResult.framesPerSecond = framesPerSecond; + pigeonResult.videoBitrate = videoBitrate; + pigeonResult.audioBitrate = audioBitrate; + pigeonResult.enableAudio = enableAudio; + return pigeonResult; +} ++ (FCPPlatformMediaSettings *)fromList:(NSArray *)list { + FCPPlatformMediaSettings *pigeonResult = [[FCPPlatformMediaSettings alloc] init]; + pigeonResult.resolutionPreset = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.framesPerSecond = GetNullableObjectAtIndex(list, 1); + pigeonResult.videoBitrate = GetNullableObjectAtIndex(list, 2); + pigeonResult.audioBitrate = GetNullableObjectAtIndex(list, 3); + pigeonResult.enableAudio = [GetNullableObjectAtIndex(list, 4) boolValue]; + return pigeonResult; +} ++ (nullable FCPPlatformMediaSettings *)nullableFromList:(NSArray *)list { + return (list) ? [FCPPlatformMediaSettings fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.resolutionPreset), + self.framesPerSecond ?: [NSNull null], + self.videoBitrate ?: [NSNull null], + self.audioBitrate ?: [NSNull null], + @(self.enableAudio), + ]; +} +@end + +@implementation FCPPlatformPoint ++ (instancetype)makeWithX:(double)x y:(double)y { + FCPPlatformPoint *pigeonResult = [[FCPPlatformPoint alloc] init]; + pigeonResult.x = x; + pigeonResult.y = y; + return pigeonResult; +} ++ (FCPPlatformPoint *)fromList:(NSArray *)list { + FCPPlatformPoint *pigeonResult = [[FCPPlatformPoint alloc] init]; + pigeonResult.x = [GetNullableObjectAtIndex(list, 0) doubleValue]; + pigeonResult.y = [GetNullableObjectAtIndex(list, 1) doubleValue]; + return pigeonResult; +} ++ (nullable FCPPlatformPoint *)nullableFromList:(NSArray *)list { + return (list) ? [FCPPlatformPoint fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.x), + @(self.y), + ]; +} +@end + @implementation FCPPlatformSize + (instancetype)makeWithWidth:(double)width height:(double)height { FCPPlatformSize *pigeonResult = [[FCPPlatformSize alloc] init]; @@ -190,6 +304,10 @@ - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FCPPlatformCameraDescription fromList:[self readValue]]; + case 129: + return [FCPPlatformMediaSettings fromList:[self readValue]]; + case 130: + return [FCPPlatformPoint fromList:[self readValue]]; default: return [super readValueOfType:type]; } @@ -203,6 +321,12 @@ - (void)writeValue:(id)value { if ([value isKindOfClass:[FCPPlatformCameraDescription class]]) { [self writeByte:128]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FCPPlatformMediaSettings class]]) { + [self writeByte:129]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FCPPlatformPoint class]]) { + [self writeByte:130]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -264,6 +388,733 @@ void SetUpFCPCameraApiWithSuffix(id binaryMessenger, [channel setMessageHandler:nil]; } } + /// Create a new camera with the given settings, and returns its ID. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.create", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(createCameraWithName:settings:completion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(createCameraWithName:settings:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSString *arg_cameraName = GetNullableObjectAtIndex(args, 0); + FCPPlatformMediaSettings *arg_settings = GetNullableObjectAtIndex(args, 1); + [api createCameraWithName:arg_cameraName + settings:arg_settings + completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Initializes the camera with the given ID. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + [NSString + stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.initialize", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(initializeCamera:withImageFormat:completion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(initializeCamera:withImageFormat:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSInteger arg_cameraId = [GetNullableObjectAtIndex(args, 0) integerValue]; + FCPPlatformImageFormatGroup arg_imageFormat = + [GetNullableObjectAtIndex(args, 1) integerValue]; + [api initializeCamera:arg_cameraId + withImageFormat:arg_imageFormat + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Begins streaming frames from the camera. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.startImageStream", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(startImageStreamWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(startImageStreamWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api startImageStreamWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Stops streaming frames from the camera. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.stopImageStream", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(stopImageStreamWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(stopImageStreamWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api stopImageStreamWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Called by the Dart side of the plugin when it has received the last image + /// frame sent. + /// + /// This is used to throttle sending frames across the channel. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.receivedImageStreamData", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(receivedImageStreamDataWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(receivedImageStreamDataWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api receivedImageStreamDataWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Indicates that the given camera is no longer being used on the Dart side, + /// and any associated resources can be cleaned up. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.dispose", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(disposeCamera:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(disposeCamera:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSInteger arg_cameraId = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api disposeCamera:arg_cameraId + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Locks the camera capture to the current device orientation. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.lockCaptureOrientation", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(lockCaptureOrientation:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(lockCaptureOrientation:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformDeviceOrientation arg_orientation = + [GetNullableObjectAtIndex(args, 0) integerValue]; + [api lockCaptureOrientation:arg_orientation + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Unlocks camera capture orientation, allowing it to automatically adapt to + /// device orientation. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.unlockCaptureOrientation", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(unlockCaptureOrientationWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(unlockCaptureOrientationWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api unlockCaptureOrientationWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Takes a picture with the current settings, and returns the path to the + /// resulting file. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + [NSString + stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.takePicture", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(takePictureWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(takePictureWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api + takePictureWithCompletion:^(NSString *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Does any preprocessing necessary before beginning to record video. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.prepareForVideoRecording", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(prepareForVideoRecordingWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(prepareForVideoRecordingWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api prepareForVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Begins recording video, optionally enabling streaming to Dart at the same + /// time. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.startVideoRecording", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(startVideoRecordingWithStreaming:completion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(startVideoRecordingWithStreaming:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + BOOL arg_enableStream = [GetNullableObjectAtIndex(args, 0) boolValue]; + [api startVideoRecordingWithStreaming:arg_enableStream + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Stops recording video, and results the path to the resulting file. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.stopVideoRecording", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(stopVideoRecordingWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(stopVideoRecordingWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api stopVideoRecordingWithCompletion:^(NSString *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Pauses video recording. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.pauseVideoRecording", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(pauseVideoRecordingWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(pauseVideoRecordingWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api pauseVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Resumes a previously paused video recording. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.resumeVideoRecording", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(resumeVideoRecordingWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(resumeVideoRecordingWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api resumeVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Switches the camera to the given flash mode. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.setFlashMode", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setFlashMode:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setFlashMode:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformFlashMode arg_mode = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api setFlashMode:arg_mode + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Switches the camera to the given exposure mode. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.setExposureMode", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setExposureMode:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setExposureMode:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformExposureMode arg_mode = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api setExposureMode:arg_mode + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Anchors auto-exposure to the given point in (0,1) coordinate space. + /// + /// A null value resets to the default exposure point. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.setExposurePoint", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setExposurePoint:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setExposurePoint:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformPoint *arg_point = GetNullableObjectAtIndex(args, 0); + [api setExposurePoint:arg_point + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns the minimum exposure offset supported by the camera. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.getMinExposureOffset", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(getMinimumExposureOffset:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(getMinimumExposureOffset:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api getMinimumExposureOffset:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns the maximum exposure offset supported by the camera. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.getMaxExposureOffset", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(getMaximumExposureOffset:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(getMaximumExposureOffset:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api getMaximumExposureOffset:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Sets the exposure offset manually to the given value. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.setExposureOffset", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(setExposureOffset:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setExposureOffset:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + double arg_offset = [GetNullableObjectAtIndex(args, 0) doubleValue]; + [api setExposureOffset:arg_offset + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Switches the camera to the given focus mode. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.setFocusMode", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setFocusMode:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setFocusMode:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformFocusMode arg_mode = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api setFocusMode:arg_mode + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Anchors auto-focus to the given point in (0,1) coordinate space. + /// + /// A null value resets to the default focus point. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.setFocusPoint", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setFocusPoint:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setFocusPoint:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformPoint *arg_point = GetNullableObjectAtIndex(args, 0); + [api setFocusPoint:arg_point + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns the minimum zoom level supported by the camera. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.getMinZoomLevel", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(getMinimumZoomLevel:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(getMinimumZoomLevel:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api getMinimumZoomLevel:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns the maximum zoom level supported by the camera. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.getMaxZoomLevel", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(getMaximumZoomLevel:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(getMaximumZoomLevel:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api getMaximumZoomLevel:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Sets the zoom factor. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.setZoomLevel", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setZoomLevel:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setZoomLevel:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + double arg_zoom = [GetNullableObjectAtIndex(args, 0) doubleValue]; + [api setZoomLevel:arg_zoom + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Pauses streaming of preview frames. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.pausePreview", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(pausePreviewWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(pausePreviewWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api pausePreviewWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Resumes a previously paused preview stream. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.camera_avfoundation.CameraApi.resumePreview", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(resumePreviewWithCompletion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(resumePreviewWithCompletion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + [api resumePreviewWithCompletion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Changes the camera used while recording video. + /// + /// This should only be called while video recording is active. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.updateDescriptionWhileRecording", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(updateDescriptionWhileRecordingCameraName: + completion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(updateDescriptionWhileRecordingCameraName:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSString *arg_cameraName = GetNullableObjectAtIndex(args, 0); + [api updateDescriptionWhileRecordingCameraName:arg_cameraName + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Sets the file format used for taking pictures. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.setImageFileFormat", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(setImageFileFormat:completion:)], + @"FCPCameraApi api (%@) doesn't respond to @selector(setImageFileFormat:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformImageFileFormat arg_format = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api setImageFileFormat:arg_format + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } } NSObject *FCPCameraGlobalEventApiGetCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index dc9f3c74d82..6f947863a3c 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -16,9 +15,6 @@ import 'messages.g.dart'; import 'type_conversion.dart'; import 'utils.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/camera_avfoundation'); - /// An iOS implementation of [CameraPlatform] based on AVFoundation. class AVFoundationCamera extends CameraPlatform { /// Creates a new AVFoundation-based [CameraPlatform] implementation instance. @@ -100,19 +96,16 @@ class AVFoundationCamera extends CameraPlatform { MediaSettings? mediaSettings, ) async { try { - final Map? reply = await _channel - .invokeMapMethod('create', { - 'cameraName': cameraDescription.name, - 'resolutionPreset': null != mediaSettings?.resolutionPreset - ? _serializeResolutionPreset(mediaSettings!.resolutionPreset!) - : null, - 'fps': mediaSettings?.fps, - 'videoBitrate': mediaSettings?.videoBitrate, - 'audioBitrate': mediaSettings?.audioBitrate, - 'enableAudio': mediaSettings?.enableAudio ?? true, - }); - - return reply!['cameraId']! as int; + return await _hostApi.create( + cameraDescription.name, + PlatformMediaSettings( + resolutionPreset: + _pigeonResolutionPreset(mediaSettings?.resolutionPreset), + framesPerSecond: mediaSettings?.fps, + videoBitrate: mediaSettings?.videoBitrate, + audioBitrate: mediaSettings?.audioBitrate, + enableAudio: mediaSettings?.enableAudio ?? true, + )); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -122,38 +115,26 @@ class AVFoundationCamera extends CameraPlatform { Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, - }) { + }) async { hostCameraHandlers.putIfAbsent(cameraId, () => HostCameraMessageHandler(cameraId, cameraEventStreamController)); final Completer completer = Completer(); - onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { + unawaited(onCameraInitialized(cameraId) + .first + .then((CameraInitializedEvent value) { completer.complete(); - }); + })); - _channel.invokeMapMethod( - 'initialize', - { - 'cameraId': cameraId, - 'imageFormatGroup': imageFormatGroup.name(), - }, - ).catchError( - // TODO(srawlins): This should return a value of the future's type. This - // will fail upcoming analysis checks with - // https://github.com/flutter/flutter/issues/105750. - // ignore: body_might_complete_normally_catch_error - (Object error, StackTrace stackTrace) { - if (error is! PlatformException) { - // ignore: only_throw_errors - throw error; - } - completer.completeError( - CameraException(error.code, error.message), - stackTrace, - ); - }, - ); + try { + await _hostApi.initialize(cameraId, _pigeonImageFormat(imageFormatGroup)); + } on PlatformException catch (e, s) { + completer.completeError( + CameraException(e.code, e.message), + s, + ); + } return completer.future; } @@ -164,10 +145,7 @@ class AVFoundationCamera extends CameraPlatform { hostCameraHandlers.remove(cameraId); handler?.dispose(); - await _channel.invokeMethod( - 'dispose', - {'cameraId': cameraId}, - ); + await _hostApi.dispose(cameraId); } @override @@ -206,43 +184,25 @@ class AVFoundationCamera extends CameraPlatform { int cameraId, DeviceOrientation orientation, ) async { - await _channel.invokeMethod( - 'lockCaptureOrientation', - { - 'cameraId': cameraId, - 'orientation': serializeDeviceOrientation(orientation) - }, - ); + await _hostApi + .lockCaptureOrientation(serializeDeviceOrientation(orientation)); } @override Future unlockCaptureOrientation(int cameraId) async { - await _channel.invokeMethod( - 'unlockCaptureOrientation', - {'cameraId': cameraId}, - ); + await _hostApi.unlockCaptureOrientation(); } @override Future takePicture(int cameraId) async { - final String? path = await _channel.invokeMethod( - 'takePicture', - {'cameraId': cameraId}, - ); - - if (path == null) { - throw CameraException( - 'INVALID_PATH', - 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', - ); - } - + final String path = await _hostApi.takePicture(); return XFile(path); } @override - Future prepareForVideoRecording() => - _channel.invokeMethod('prepareForVideoRecording'); + Future prepareForVideoRecording() async { + await _hostApi.prepareForVideoRecording(); + } @override Future startVideoRecording(int cameraId, @@ -253,14 +213,8 @@ class AVFoundationCamera extends CameraPlatform { @override Future startVideoCapturing(VideoCaptureOptions options) async { - await _channel.invokeMethod( - 'startVideoRecording', - { - 'cameraId': options.cameraId, - 'maxVideoDuration': options.maxDuration?.inMilliseconds, - 'enableStream': options.streamCallback != null, - }, - ); + // Max video duration is currently not supported. + await _hostApi.startVideoRecording(options.streamCallback != null); if (options.streamCallback != null) { _frameStreamController = _createStreamController(); @@ -271,33 +225,19 @@ class AVFoundationCamera extends CameraPlatform { @override Future stopVideoRecording(int cameraId) async { - final String? path = await _channel.invokeMethod( - 'stopVideoRecording', - {'cameraId': cameraId}, - ); - - if (path == null) { - throw CameraException( - 'INVALID_PATH', - 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', - ); - } - + final String path = await _hostApi.stopVideoRecording(); return XFile(path); } @override - Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( - 'pauseVideoRecording', - {'cameraId': cameraId}, - ); + Future pauseVideoRecording(int cameraId) async { + await _hostApi.pauseVideoRecording(); + } @override - Future resumeVideoRecording(int cameraId) => - _channel.invokeMethod( - 'resumeVideoRecording', - {'cameraId': cameraId}, - ); + Future resumeVideoRecording(int cameraId) async { + await _hostApi.resumeVideoRecording(); + } @override Stream onStreamedFrameAvailable(int cameraId, @@ -322,7 +262,7 @@ class AVFoundationCamera extends CameraPlatform { } Future _startPlatformStream() async { - await _channel.invokeMethod('startImageStream'); + await _hostApi.startImageStream(); _startStreamListener(); } @@ -332,7 +272,7 @@ class AVFoundationCamera extends CameraPlatform { _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { try { - _channel.invokeMethod('receivedImageStreamData'); + _hostApi.receivedImageStreamData(); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -342,7 +282,7 @@ class AVFoundationCamera extends CameraPlatform { } FutureOr _onFrameStreamCancel() async { - await _channel.invokeMethod('stopImageStream'); + await _hostApi.stopImageStream(); await _platformImageStreamSubscription?.cancel(); _platformImageStreamSubscription = null; _frameStreamController = null; @@ -354,140 +294,75 @@ class AVFoundationCamera extends CameraPlatform { } @override - Future setFlashMode(int cameraId, FlashMode mode) => - _channel.invokeMethod( - 'setFlashMode', - { - 'cameraId': cameraId, - 'mode': _serializeFlashMode(mode), - }, - ); + Future setFlashMode(int cameraId, FlashMode mode) async { + await _hostApi.setFlashMode(_pigeonFlashMode(mode)); + } @override - Future setExposureMode(int cameraId, ExposureMode mode) => - _channel.invokeMethod( - 'setExposureMode', - { - 'cameraId': cameraId, - 'mode': _serializeExposureMode(mode), - }, - ); + Future setExposureMode(int cameraId, ExposureMode mode) async { + await _hostApi.setExposureMode(_pigeonExposureMode(mode)); + } @override - Future setExposurePoint(int cameraId, Point? point) { + Future setExposurePoint(int cameraId, Point? point) async { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); - return _channel.invokeMethod( - 'setExposurePoint', - { - 'cameraId': cameraId, - 'reset': point == null, - 'x': point?.x, - 'y': point?.y, - }, - ); + await _hostApi.setExposurePoint(_pigeonPoint(point)); } @override Future getMinExposureOffset(int cameraId) async { - final double? minExposureOffset = await _channel.invokeMethod( - 'getMinExposureOffset', - {'cameraId': cameraId}, - ); - - return minExposureOffset!; + return _hostApi.getMinExposureOffset(); } @override Future getMaxExposureOffset(int cameraId) async { - final double? maxExposureOffset = await _channel.invokeMethod( - 'getMaxExposureOffset', - {'cameraId': cameraId}, - ); - - return maxExposureOffset!; + return _hostApi.getMaxExposureOffset(); } @override Future getExposureOffsetStepSize(int cameraId) async { - final double? stepSize = await _channel.invokeMethod( - 'getExposureOffsetStepSize', - {'cameraId': cameraId}, - ); - - return stepSize!; + // iOS has no step size. + return 0; } @override Future setExposureOffset(int cameraId, double offset) async { - final double? appliedOffset = await _channel.invokeMethod( - 'setExposureOffset', - { - 'cameraId': cameraId, - 'offset': offset, - }, - ); - - return appliedOffset!; + await _hostApi.setExposureOffset(offset); + // The platform API allows for implementations that have to adjust the + // target offset and return the actual offset used, but there is never + // adjustment in this implementation. + return offset; } @override - Future setFocusMode(int cameraId, FocusMode mode) => - _channel.invokeMethod( - 'setFocusMode', - { - 'cameraId': cameraId, - 'mode': _serializeFocusMode(mode), - }, - ); + Future setFocusMode(int cameraId, FocusMode mode) async { + await _hostApi.setFocusMode(_pigeonFocusMode(mode)); + } @override - Future setFocusPoint(int cameraId, Point? point) { + Future setFocusPoint(int cameraId, Point? point) async { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); - return _channel.invokeMethod( - 'setFocusPoint', - { - 'cameraId': cameraId, - 'reset': point == null, - 'x': point?.x, - 'y': point?.y, - }, - ); + await _hostApi.setFocusPoint(_pigeonPoint(point)); } @override Future getMaxZoomLevel(int cameraId) async { - final double? maxZoomLevel = await _channel.invokeMethod( - 'getMaxZoomLevel', - {'cameraId': cameraId}, - ); - - return maxZoomLevel!; + return _hostApi.getMaxZoomLevel(); } @override Future getMinZoomLevel(int cameraId) async { - final double? minZoomLevel = await _channel.invokeMethod( - 'getMinZoomLevel', - {'cameraId': cameraId}, - ); - - return minZoomLevel!; + return _hostApi.getMinZoomLevel(); } @override Future setZoomLevel(int cameraId, double zoom) async { try { - await _channel.invokeMethod( - 'setZoomLevel', - { - 'cameraId': cameraId, - 'zoom': zoom, - }, - ); + await _hostApi.setZoomLevel(zoom); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -495,40 +370,23 @@ class AVFoundationCamera extends CameraPlatform { @override Future pausePreview(int cameraId) async { - await _channel.invokeMethod( - 'pausePreview', - {'cameraId': cameraId}, - ); + await _hostApi.pausePreview(); } @override Future resumePreview(int cameraId) async { - await _channel.invokeMethod( - 'resumePreview', - {'cameraId': cameraId}, - ); + await _hostApi.resumePreview(); } @override Future setDescriptionWhileRecording( CameraDescription description) async { - await _channel.invokeMethod( - 'setDescriptionWhileRecording', - { - 'cameraName': description.name, - }, - ); + await _hostApi.updateDescriptionWhileRecording(description.name); } @override - Future setImageFileFormat(int cameraId, ImageFileFormat format) { - return _channel.invokeMethod( - 'setImageFileFormat', - { - 'cameraId': cameraId, - 'fileFormat': format.name, - }, - ); + Future setImageFileFormat(int cameraId, ImageFileFormat format) async { + await _hostApi.setImageFileFormat(_pigeonImageFileFormat(format)); } @override @@ -536,12 +394,13 @@ class AVFoundationCamera extends CameraPlatform { return Texture(textureId: cameraId); } - String _serializeFocusMode(FocusMode mode) { + /// Returns an [FocusMode]'s Pigeon representation. + PlatformFocusMode _pigeonFocusMode(FocusMode mode) { switch (mode) { case FocusMode.locked: - return 'locked'; + return PlatformFocusMode.locked; case FocusMode.auto: - return 'auto'; + return PlatformFocusMode.auto; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used @@ -549,15 +408,16 @@ class AVFoundationCamera extends CameraPlatform { // the switch rather than a `default` so that the linter will flag the // switch as needing an update. // ignore: dead_code - return 'auto'; + return PlatformFocusMode.auto; } - String _serializeExposureMode(ExposureMode mode) { + /// Returns an [ExposureMode]'s Pigeon representation. + PlatformExposureMode _pigeonExposureMode(ExposureMode mode) { switch (mode) { case ExposureMode.locked: - return 'locked'; + return PlatformExposureMode.locked; case ExposureMode.auto: - return 'auto'; + return PlatformExposureMode.auto; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used @@ -565,20 +425,20 @@ class AVFoundationCamera extends CameraPlatform { // the switch rather than a `default` so that the linter will flag the // switch as needing an update. // ignore: dead_code - return 'auto'; + return PlatformExposureMode.auto; } - /// Returns the flash mode as a String. - String _serializeFlashMode(FlashMode flashMode) { + /// Returns a [FlashMode]'s Pigeon representation. + PlatformFlashMode _pigeonFlashMode(FlashMode flashMode) { switch (flashMode) { case FlashMode.off: - return 'off'; + return PlatformFlashMode.off; case FlashMode.auto: - return 'auto'; + return PlatformFlashMode.auto; case FlashMode.always: - return 'always'; + return PlatformFlashMode.always; case FlashMode.torch: - return 'torch'; + return PlatformFlashMode.torch; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used @@ -586,24 +446,30 @@ class AVFoundationCamera extends CameraPlatform { // the switch rather than a `default` so that the linter will flag the // switch as needing an update. // ignore: dead_code - return 'off'; + return PlatformFlashMode.off; } - /// Returns the resolution preset as a String. - String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + /// Returns a [ResolutionPreset]'s Pigeon representation. + PlatformResolutionPreset _pigeonResolutionPreset( + ResolutionPreset? resolutionPreset) { + if (resolutionPreset == null) { + // Provide a default if one isn't provided, since the native side needs + // to set something. + return PlatformResolutionPreset.high; + } switch (resolutionPreset) { case ResolutionPreset.max: - return 'max'; + return PlatformResolutionPreset.max; case ResolutionPreset.ultraHigh: - return 'ultraHigh'; + return PlatformResolutionPreset.ultraHigh; case ResolutionPreset.veryHigh: - return 'veryHigh'; + return PlatformResolutionPreset.veryHigh; case ResolutionPreset.high: - return 'high'; + return PlatformResolutionPreset.high; case ResolutionPreset.medium: - return 'medium'; + return PlatformResolutionPreset.medium; case ResolutionPreset.low: - return 'low'; + return PlatformResolutionPreset.low; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used @@ -611,7 +477,59 @@ class AVFoundationCamera extends CameraPlatform { // the switch rather than a `default` so that the linter will flag the // switch as needing an update. // ignore: dead_code - return 'max'; + return PlatformResolutionPreset.max; + } + + /// Returns an [ImageFormatGroup]'s Pigeon representation. + PlatformImageFormatGroup _pigeonImageFormat(ImageFormatGroup format) { + switch (format) { + // "unknown" is used to indicate the default. + case ImageFormatGroup.unknown: + case ImageFormatGroup.bgra8888: + return PlatformImageFormatGroup.bgra8888; + case ImageFormatGroup.yuv420: + return PlatformImageFormatGroup.yuv420; + case ImageFormatGroup.jpeg: + case ImageFormatGroup.nv21: + // Fall through. + } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // TODO(stuartmorgan): Consider throwing an UnsupportedError, instead of + // doing fallback, when a specific unsupported format is requested. This + // would require a breaking change at this layer and the app-facing layer. + return PlatformImageFormatGroup.bgra8888; + } + + /// Returns an [ImageFileFormat]'s Pigeon representation. + PlatformImageFileFormat _pigeonImageFileFormat(ImageFileFormat format) { + switch (format) { + case ImageFileFormat.heif: + return PlatformImageFileFormat.heif; + case ImageFileFormat.jpeg: + return PlatformImageFileFormat.jpeg; + } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // TODO(stuartmorgan): Consider throwing an UnsupportedError, instead of + // doing fallback, when a specific unsupported format is requested. This + // would require a breaking change at this layer and the app-facing layer. + // ignore: dead_code + return PlatformImageFileFormat.jpeg; + } + + /// Returns a [Point]s Pigeon representation. + PlatformPoint? _pigeonPoint(Point? point) { + if (point == null) { + return null; + } + return PlatformPoint(x: point.x, y: point.y); } } diff --git a/packages/camera/camera_avfoundation/lib/src/messages.g.dart b/packages/camera/camera_avfoundation/lib/src/messages.g.dart index a4b399217eb..4290eb02ed2 100644 --- a/packages/camera/camera_avfoundation/lib/src/messages.g.dart +++ b/packages/camera/camera_avfoundation/lib/src/messages.g.dart @@ -52,11 +52,38 @@ enum PlatformExposureMode { locked, } +enum PlatformFlashMode { + off, + auto, + always, + torch, +} + enum PlatformFocusMode { auto, locked, } +/// Pigeon version of ImageFileFormat. +enum PlatformImageFileFormat { + jpeg, + heif, +} + +enum PlatformImageFormatGroup { + bgra8888, + yuv420, +} + +enum PlatformResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} + class PlatformCameraDescription { PlatformCameraDescription({ required this.name, @@ -131,6 +158,73 @@ class PlatformCameraState { } } +class PlatformMediaSettings { + PlatformMediaSettings({ + required this.resolutionPreset, + this.framesPerSecond, + this.videoBitrate, + this.audioBitrate, + required this.enableAudio, + }); + + PlatformResolutionPreset resolutionPreset; + + int? framesPerSecond; + + int? videoBitrate; + + int? audioBitrate; + + bool enableAudio; + + Object encode() { + return [ + resolutionPreset.index, + framesPerSecond, + videoBitrate, + audioBitrate, + enableAudio, + ]; + } + + static PlatformMediaSettings decode(Object result) { + result as List; + return PlatformMediaSettings( + resolutionPreset: PlatformResolutionPreset.values[result[0]! as int], + framesPerSecond: result[1] as int?, + videoBitrate: result[2] as int?, + audioBitrate: result[3] as int?, + enableAudio: result[4]! as bool, + ); + } +} + +class PlatformPoint { + PlatformPoint({ + required this.x, + required this.y, + }); + + double x; + + double y; + + Object encode() { + return [ + x, + y, + ]; + } + + static PlatformPoint decode(Object result) { + result as List; + return PlatformPoint( + x: result[0]! as double, + y: result[1]! as double, + ); + } +} + class PlatformSize { PlatformSize({ required this.width, @@ -164,6 +258,12 @@ class _CameraApiCodec extends StandardMessageCodec { if (value is PlatformCameraDescription) { buffer.putUint8(128); writeValue(buffer, value.encode()); + } else if (value is PlatformMediaSettings) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is PlatformPoint) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -174,6 +274,10 @@ class _CameraApiCodec extends StandardMessageCodec { switch (type) { case 128: return PlatformCameraDescription.decode(readValue(buffer)!); + case 129: + return PlatformMediaSettings.decode(readValue(buffer)!); + case 130: + return PlatformPoint.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -225,6 +329,781 @@ class CameraApi { .cast(); } } + + /// Create a new camera with the given settings, and returns its ID. + Future create(String cameraName, PlatformMediaSettings settings) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.create$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([cameraName, settings]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as int?)!; + } + } + + /// Initializes the camera with the given ID. + Future initialize( + int cameraId, PlatformImageFormatGroup imageFormat) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.initialize$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([cameraId, imageFormat.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Begins streaming frames from the camera. + Future startImageStream() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.startImageStream$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Stops streaming frames from the camera. + Future stopImageStream() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.stopImageStream$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Called by the Dart side of the plugin when it has received the last image + /// frame sent. + /// + /// This is used to throttle sending frames across the channel. + Future receivedImageStreamData() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.receivedImageStreamData$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Indicates that the given camera is no longer being used on the Dart side, + /// and any associated resources can be cleaned up. + Future dispose(int cameraId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.dispose$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([cameraId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Locks the camera capture to the current device orientation. + Future lockCaptureOrientation( + PlatformDeviceOrientation orientation) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.lockCaptureOrientation$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([orientation.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Unlocks camera capture orientation, allowing it to automatically adapt to + /// device orientation. + Future unlockCaptureOrientation() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.unlockCaptureOrientation$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Takes a picture with the current settings, and returns the path to the + /// resulting file. + Future takePicture() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.takePicture$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + /// Does any preprocessing necessary before beginning to record video. + Future prepareForVideoRecording() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.prepareForVideoRecording$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Begins recording video, optionally enabling streaming to Dart at the same + /// time. + Future startVideoRecording(bool enableStream) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.startVideoRecording$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([enableStream]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Stops recording video, and results the path to the resulting file. + Future stopVideoRecording() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.stopVideoRecording$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as String?)!; + } + } + + /// Pauses video recording. + Future pauseVideoRecording() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.pauseVideoRecording$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Resumes a previously paused video recording. + Future resumeVideoRecording() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.resumeVideoRecording$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Switches the camera to the given flash mode. + Future setFlashMode(PlatformFlashMode mode) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setFlashMode$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([mode.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Switches the camera to the given exposure mode. + Future setExposureMode(PlatformExposureMode mode) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setExposureMode$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([mode.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Anchors auto-exposure to the given point in (0,1) coordinate space. + /// + /// A null value resets to the default exposure point. + Future setExposurePoint(PlatformPoint? point) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setExposurePoint$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([point]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns the minimum exposure offset supported by the camera. + Future getMinExposureOffset() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.getMinExposureOffset$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Returns the maximum exposure offset supported by the camera. + Future getMaxExposureOffset() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.getMaxExposureOffset$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Sets the exposure offset manually to the given value. + Future setExposureOffset(double offset) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setExposureOffset$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([offset]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Switches the camera to the given focus mode. + Future setFocusMode(PlatformFocusMode mode) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setFocusMode$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([mode.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Anchors auto-focus to the given point in (0,1) coordinate space. + /// + /// A null value resets to the default focus point. + Future setFocusPoint(PlatformPoint? point) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setFocusPoint$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([point]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns the minimum zoom level supported by the camera. + Future getMinZoomLevel() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.getMinZoomLevel$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Returns the maximum zoom level supported by the camera. + Future getMaxZoomLevel() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.getMaxZoomLevel$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Sets the zoom factor. + Future setZoomLevel(double zoom) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setZoomLevel$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([zoom]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Pauses streaming of preview frames. + Future pausePreview() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.pausePreview$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Resumes a previously paused preview stream. + Future resumePreview() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.resumePreview$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Changes the camera used while recording video. + /// + /// This should only be called while video recording is active. + Future updateDescriptionWhileRecording(String cameraName) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.updateDescriptionWhileRecording$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([cameraName]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Sets the file format used for taking pictures. + Future setImageFileFormat(PlatformImageFileFormat format) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setImageFileFormat$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([format.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } } /// Handler for native callbacks that are not tied to a specific camera ID. diff --git a/packages/camera/camera_avfoundation/lib/src/utils.dart b/packages/camera/camera_avfoundation/lib/src/utils.dart index 5c38f809eba..3fd7f59a891 100644 --- a/packages/camera/camera_avfoundation/lib/src/utils.dart +++ b/packages/camera/camera_avfoundation/lib/src/utils.dart @@ -26,17 +26,18 @@ CameraLensDirection cameraLensDirectionFromPlatform( }; } -/// Returns the device orientation as a String. -String serializeDeviceOrientation(DeviceOrientation orientation) { +/// Convents the given device orientation to Pigeon. +PlatformDeviceOrientation serializeDeviceOrientation( + DeviceOrientation orientation) { switch (orientation) { case DeviceOrientation.portraitUp: - return 'portraitUp'; + return PlatformDeviceOrientation.portraitUp; case DeviceOrientation.portraitDown: - return 'portraitDown'; + return PlatformDeviceOrientation.portraitDown; case DeviceOrientation.landscapeRight: - return 'landscapeRight'; + return PlatformDeviceOrientation.landscapeRight; case DeviceOrientation.landscapeLeft: - return 'landscapeLeft'; + return PlatformDeviceOrientation.landscapeLeft; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used @@ -44,7 +45,7 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { // the switch rather than a `default` so that the linter will flag the // switch as needing an update. // ignore: dead_code - return 'portraitUp'; + return PlatformDeviceOrientation.portraitUp; } /// Converts a Pigeon [PlatformDeviceOrientation] to a [DeviceOrientation]. diff --git a/packages/camera/camera_avfoundation/pigeons/messages.dart b/packages/camera/camera_avfoundation/pigeons/messages.dart index e88b9cc7aef..f99e03f2a4d 100644 --- a/packages/camera/camera_avfoundation/pigeons/messages.dart +++ b/packages/camera/camera_avfoundation/pigeons/messages.dart @@ -38,12 +38,42 @@ enum PlatformExposureMode { locked, } +// Pigeon version of FlashMode. +enum PlatformFlashMode { + off, + auto, + always, + torch, +} + // Pigeon version of FocusMode. enum PlatformFocusMode { auto, locked, } +/// Pigeon version of ImageFileFormat. +enum PlatformImageFileFormat { + jpeg, + heif, +} + +// Pigeon version of the subset of ImageFormatGroup supported on iOS. +enum PlatformImageFormatGroup { + bgra8888, + yuv420, +} + +// Pigeon version of ResolutionPreset. +enum PlatformResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} + // Pigeon version of CameraDescription. class PlatformCameraDescription { PlatformCameraDescription({ @@ -84,6 +114,31 @@ class PlatformCameraState { final bool focusPointSupported; } +// Pigeon version of to MediaSettings. +class PlatformMediaSettings { + PlatformMediaSettings({ + required this.resolutionPreset, + required this.framesPerSecond, + required this.videoBitrate, + required this.audioBitrate, + required this.enableAudio, + }); + + final PlatformResolutionPreset resolutionPreset; + final int? framesPerSecond; + final int? videoBitrate; + final int? audioBitrate; + final bool enableAudio; +} + +// Pigeon equivalent of CGPoint. +class PlatformPoint { + PlatformPoint({required this.x, required this.y}); + + final double x; + final double y; +} + // Pigeon equivalent of CGSize. class PlatformSize { PlatformSize({required this.width, required this.height}); @@ -101,6 +156,152 @@ abstract class CameraApi { @async @ObjCSelector('availableCamerasWithCompletion') List getAvailableCameras(); + + /// Create a new camera with the given settings, and returns its ID. + @async + @ObjCSelector('createCameraWithName:settings:') + int create(String cameraName, PlatformMediaSettings settings); + + /// Initializes the camera with the given ID. + @async + @ObjCSelector('initializeCamera:withImageFormat:') + void initialize(int cameraId, PlatformImageFormatGroup imageFormat); + + /// Begins streaming frames from the camera. + @async + void startImageStream(); + + /// Stops streaming frames from the camera. + @async + void stopImageStream(); + + /// Called by the Dart side of the plugin when it has received the last image + /// frame sent. + /// + /// This is used to throttle sending frames across the channel. + @async + void receivedImageStreamData(); + + /// Indicates that the given camera is no longer being used on the Dart side, + /// and any associated resources can be cleaned up. + @async + @ObjCSelector('disposeCamera:') + void dispose(int cameraId); + + /// Locks the camera capture to the current device orientation. + @async + @ObjCSelector('lockCaptureOrientation:') + void lockCaptureOrientation(PlatformDeviceOrientation orientation); + + /// Unlocks camera capture orientation, allowing it to automatically adapt to + /// device orientation. + @async + void unlockCaptureOrientation(); + + /// Takes a picture with the current settings, and returns the path to the + /// resulting file. + @async + String takePicture(); + + /// Does any preprocessing necessary before beginning to record video. + @async + void prepareForVideoRecording(); + + /// Begins recording video, optionally enabling streaming to Dart at the same + /// time. + @async + @ObjCSelector('startVideoRecordingWithStreaming:') + void startVideoRecording(bool enableStream); + + /// Stops recording video, and results the path to the resulting file. + @async + String stopVideoRecording(); + + /// Pauses video recording. + @async + void pauseVideoRecording(); + + /// Resumes a previously paused video recording. + @async + void resumeVideoRecording(); + + /// Switches the camera to the given flash mode. + @async + @ObjCSelector('setFlashMode:') + void setFlashMode(PlatformFlashMode mode); + + /// Switches the camera to the given exposure mode. + @async + @ObjCSelector('setExposureMode:') + void setExposureMode(PlatformExposureMode mode); + + /// Anchors auto-exposure to the given point in (0,1) coordinate space. + /// + /// A null value resets to the default exposure point. + @async + @ObjCSelector('setExposurePoint:') + void setExposurePoint(PlatformPoint? point); + + /// Returns the minimum exposure offset supported by the camera. + @async + @ObjCSelector('getMinimumExposureOffset') + double getMinExposureOffset(); + + /// Returns the maximum exposure offset supported by the camera. + @async + @ObjCSelector('getMaximumExposureOffset') + double getMaxExposureOffset(); + + /// Sets the exposure offset manually to the given value. + @async + @ObjCSelector('setExposureOffset:') + void setExposureOffset(double offset); + + /// Switches the camera to the given focus mode. + @async + @ObjCSelector('setFocusMode:') + void setFocusMode(PlatformFocusMode mode); + + /// Anchors auto-focus to the given point in (0,1) coordinate space. + /// + /// A null value resets to the default focus point. + @async + @ObjCSelector('setFocusPoint:') + void setFocusPoint(PlatformPoint? point); + + /// Returns the minimum zoom level supported by the camera. + @async + @ObjCSelector('getMinimumZoomLevel') + double getMinZoomLevel(); + + /// Returns the maximum zoom level supported by the camera. + @async + @ObjCSelector('getMaximumZoomLevel') + double getMaxZoomLevel(); + + /// Sets the zoom factor. + @async + @ObjCSelector('setZoomLevel:') + void setZoomLevel(double zoom); + + /// Pauses streaming of preview frames. + @async + void pausePreview(); + + /// Resumes a previously paused preview stream. + @async + void resumePreview(); + + /// Changes the camera used while recording video. + /// + /// This should only be called while video recording is active. + @async + void updateDescriptionWhileRecording(String cameraName); + + /// Sets the file format used for taking pictures. + @async + @ObjCSelector('setImageFileFormat:') + void setImageFileFormat(PlatformImageFileFormat format); } /// Handler for native callbacks that are not tied to a specific camera ID. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 7cec9cd0677..b91fe76ae3f 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.15+4 +version: 0.9.16 environment: sdk: ^3.2.3 diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 5ee2dabb02a..df04b1a8e8e 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -17,11 +17,8 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'avfoundation_camera_test.mocks.dart'; -import 'method_channel_mock.dart'; -const String _channelName = 'plugins.flutter.io/camera_avfoundation'; - -@GenerateMocks([CameraApi]) +@GenerateNiceMocks(>[MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -33,39 +30,28 @@ void main() { group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - } - }); - final AVFoundationCamera camera = AVFoundationCamera(); + final MockCameraApi mockApi = MockCameraApi(); + when(mockApi.create(any, any)).thenAnswer((_) async => 1); + final AVFoundationCamera camera = AVFoundationCamera(api: mockApi); + const String cameraName = 'Test'; // Act final int cameraId = await camera.createCamera( const CameraDescription( - name: 'Test', + name: cameraName, lensDirection: CameraLensDirection.back, sensorOrientation: 0), ResolutionPreset.high, ); // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'high', - 'fps': null, - 'videoBitrate': null, - 'audioBitrate': null, - 'enableAudio': false - }, - ), - ]); + final VerificationResult verification = + verify(mockApi.create(captureAny, captureAny)); + expect(verification.captured[0], cameraName); + final PlatformMediaSettings? settings = + verification.captured[1] as PlatformMediaSettings?; + expect(settings, isNotNull); + expect(settings?.resolutionPreset, PlatformResolutionPreset.high); expect(cameraId, 1); }); @@ -73,57 +59,54 @@ void main() { 'Should send creation data and receive back a camera id using createCameraWithSettings', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - } - }); - final AVFoundationCamera camera = AVFoundationCamera(); + final MockCameraApi mockApi = MockCameraApi(); + when(mockApi.create(any, any)).thenAnswer((_) async => 1); + final AVFoundationCamera camera = AVFoundationCamera(api: mockApi); + const String cameraName = 'Test'; + const int fps = 15; + const int videoBitrate = 200000; + const int audioBitrate = 32000; // Act final int cameraId = await camera.createCameraWithSettings( const CameraDescription( - name: 'Test', + name: cameraName, lensDirection: CameraLensDirection.back, sensorOrientation: 0), const MediaSettings( resolutionPreset: ResolutionPreset.low, - fps: 15, - videoBitrate: 200000, - audioBitrate: 32000, + fps: fps, + videoBitrate: videoBitrate, + audioBitrate: audioBitrate, + enableAudio: true, ), ); // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'low', - 'fps': 15, - 'videoBitrate': 200000, - 'audioBitrate': 32000, - 'enableAudio': false - }, - ), - ]); + final VerificationResult verification = + verify(mockApi.create(captureAny, captureAny)); + expect(verification.captured[0], cameraName); + final PlatformMediaSettings? settings = + verification.captured[1] as PlatformMediaSettings?; + expect(settings, isNotNull); + expect(settings?.resolutionPreset, PlatformResolutionPreset.low); + expect(settings?.framesPerSecond, fps); + expect(settings?.videoBitrate, videoBitrate); + expect(settings?.audioBitrate, audioBitrate); + expect(settings?.enableAudio, true); expect(cameraId, 1); }); test('Should throw CameraException when create throws a PlatformException', () { // Arrange - MethodChannelMock(channelName: _channelName, methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) + const String exceptionCode = 'TESTING_ERROR_CODE'; + const String exceptionMessage = 'Mock error message used during testing.'; + final MockCameraApi mockApi = MockCameraApi(); + when(mockApi.create(any, any)).thenAnswer((_) async { + throw PlatformException(code: exceptionCode, message: exceptionMessage); }); - final AVFoundationCamera camera = AVFoundationCamera(); + final AVFoundationCamera camera = AVFoundationCamera(api: mockApi); // Act expect( @@ -137,41 +120,9 @@ void main() { ), throwsA( isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((CameraException e) => e.code, 'code', exceptionCode) .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), - ), - ); - }); - - test('Should throw CameraException when create throws a PlatformException', - () { - // Arrange - MethodChannelMock(channelName: _channelName, methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); - final AVFoundationCamera camera = AVFoundationCamera(); - - // Act - expect( - () => camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ), - throwsA( - isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), + exceptionMessage), ), ); }); @@ -180,16 +131,15 @@ void main() { 'Should throw CameraException when initialize throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: _channelName, - methods: { - 'initialize': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }, - ); - final AVFoundationCamera camera = AVFoundationCamera(); + const String exceptionCode = 'TESTING_ERROR_CODE'; + const String exceptionMessage = + 'Mock error message used during testing.'; + final MockCameraApi mockApi = MockCameraApi(); + when(mockApi.initialize(any, any)).thenAnswer((_) async { + throw PlatformException( + code: exceptionCode, message: exceptionMessage); + }); + final AVFoundationCamera camera = AVFoundationCamera(api: mockApi); // Act expect( @@ -210,16 +160,8 @@ void main() { test('Should send initialization data', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - 'initialize': null - }); - final AVFoundationCamera camera = AVFoundationCamera(); + final MockCameraApi mockApi = MockCameraApi(); + final AVFoundationCamera camera = AVFoundationCamera(api: mockApi); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -243,30 +185,17 @@ void main() { await initializeFuture; // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - isMethodCall( - 'initialize', - arguments: { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - ), - ]); + final VerificationResult verification = + verify(mockApi.initialize(captureAny, captureAny)); + expect(verification.captured[0], cameraId); + // The default when unspecified should be bgra8888. + expect(verification.captured[1], PlatformImageFormatGroup.bgra8888); }); test('Should send a disposal call on dispose', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': {'cameraId': 1}, - 'initialize': null, - 'dispose': {'cameraId': 1} - }); - - final AVFoundationCamera camera = AVFoundationCamera(); + final MockCameraApi mockApi = MockCameraApi(); + final AVFoundationCamera camera = AVFoundationCamera(api: mockApi); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -291,15 +220,9 @@ void main() { await camera.dispose(cameraId); // Assert - expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - anything, - isMethodCall( - 'dispose', - arguments: {'cameraId': 1}, - ), - ]); + final VerificationResult verification = + verify(mockApi.dispose(captureAny)); + expect(verification.captured[0], cameraId); }); }); @@ -307,14 +230,9 @@ void main() { late AVFoundationCamera camera; late int cameraId; setUp(() async { - MethodChannelMock( - channelName: _channelName, - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = AVFoundationCamera(); + final MockCameraApi mockApi = MockCameraApi(); + when(mockApi.create(any, any)).thenAnswer((_) async => 1); + camera = AVFoundationCamera(api: mockApi); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -424,13 +342,7 @@ void main() { setUp(() async { mockApi = MockCameraApi(); - MethodChannelMock( - channelName: _channelName, - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); + when(mockApi.create(any, any)).thenAnswer((_) async => 1); camera = AVFoundationCamera(api: mockApi); cameraId = await camera.createCamera( const CameraDescription( @@ -498,634 +410,300 @@ void main() { }); test('Should take a picture and return an XFile instance', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'takePicture': '/test/path.jpg'}); + const String stubPath = '/test/path.jpg'; + when(mockApi.takePicture()).thenAnswer((_) async => stubPath); - // Act final XFile file = await camera.takePicture(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('takePicture', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.jpg'); + expect(file.path, stubPath); }); test('Should prepare for video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'prepareForVideoRecording': null}, - ); - - // Act await camera.prepareForVideoRecording(); - // Assert - expect(channel.log, [ - isMethodCall('prepareForVideoRecording', arguments: null), - ]); + verify(mockApi.prepareForVideoRecording()); }); test('Should start recording a video', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'startVideoRecording': null}, - ); - - // Act await camera.startVideoRecording(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': null, - 'enableStream': false, - }), - ]); - }); - - test('Should pass maxVideoDuration when starting recording a video', - () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'startVideoRecording': null}, - ); - - // Act - await camera.startVideoRecording( - cameraId, - maxVideoDuration: const Duration(seconds: 10), - ); - - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': 10000, - 'enableStream': false, - }), - ]); + verify(mockApi.startVideoRecording(any)); }); test( 'Should pass enableStream if callback is passed when starting recording a video', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'startVideoRecording': null}, - ); - - // Act await camera.startVideoCapturing(VideoCaptureOptions(cameraId, streamCallback: (CameraImageData imageData) {})); - // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': null, - 'enableStream': true, - }), - ]); + verify(mockApi.startVideoRecording(true)); }); test('Should stop a video recording and return the file', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'stopVideoRecording': '/test/path.mp4'}, - ); + const String stubPath = '/test/path.mp4'; + when(mockApi.stopVideoRecording()).thenAnswer((_) async => stubPath); - // Act final XFile file = await camera.stopVideoRecording(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('stopVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.mp4'); + expect(file.path, stubPath); }); test('Should pause a video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'pauseVideoRecording': null}, - ); - - // Act await camera.pauseVideoRecording(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('pauseVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); + verify(mockApi.pauseVideoRecording()); }); test('Should resume a video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'resumeVideoRecording': null}, - ); - - // Act await camera.resumeVideoRecording(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('resumeVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); + verify(mockApi.resumeVideoRecording()); }); test('Should set the description while recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setDescriptionWhileRecording': null}, - ); const CameraDescription camera2Description = CameraDescription( name: 'Test2', lensDirection: CameraLensDirection.front, sensorOrientation: 0); - // Act await camera.setDescriptionWhileRecording(camera2Description); - // Assert - expect(channel.log, [ - isMethodCall('setDescriptionWhileRecording', - arguments: { - 'cameraName': camera2Description.name, - }), - ]); + verify(mockApi.updateDescriptionWhileRecording(camera2Description.name)); }); - test('Should set the flash mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setFlashMode': null}, - ); - - // Act + test('Should set the flash mode to torch', () async { await camera.setFlashMode(cameraId, FlashMode.torch); + + verify(mockApi.setFlashMode(PlatformFlashMode.torch)); + }); + + test('Should set the flash mode to always', () async { await camera.setFlashMode(cameraId, FlashMode.always); + + verify(mockApi.setFlashMode(PlatformFlashMode.always)); + }); + + test('Should set the flash mode to auto', () async { await camera.setFlashMode(cameraId, FlashMode.auto); - await camera.setFlashMode(cameraId, FlashMode.off); - // Assert - expect(channel.log, [ - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'torch' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'always' - }), - isMethodCall('setFlashMode', - arguments: {'cameraId': cameraId, 'mode': 'auto'}), - isMethodCall('setFlashMode', - arguments: {'cameraId': cameraId, 'mode': 'off'}), - ]); + verify(mockApi.setFlashMode(PlatformFlashMode.auto)); }); - test('Should set the exposure mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setExposureMode': null}, - ); + test('Should set the flash mode to off', () async { + await camera.setFlashMode(cameraId, FlashMode.off); - // Act + verify(mockApi.setFlashMode(PlatformFlashMode.off)); + }); + + test('Should set the exposure mode to auto', () async { await camera.setExposureMode(cameraId, ExposureMode.auto); + + verify(mockApi.setExposureMode(PlatformExposureMode.auto)); + }); + + test('Should set the exposure mode to locked', () async { await camera.setExposureMode(cameraId, ExposureMode.locked); - // Assert - expect(channel.log, [ - isMethodCall('setExposureMode', - arguments: {'cameraId': cameraId, 'mode': 'auto'}), - isMethodCall('setExposureMode', arguments: { - 'cameraId': cameraId, - 'mode': 'locked' - }), - ]); + verify(mockApi.setExposureMode(PlatformExposureMode.locked)); }); - test('Should set the exposure point', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setExposurePoint': null}, - ); + test('Should set the exposure point to a value', () async { + const Point point = Point(0.4, 0.6); + await camera.setExposurePoint(cameraId, point); - // Act - await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); + final VerificationResult verification = + verify(mockApi.setExposurePoint(captureAny)); + final PlatformPoint? passedPoint = + verification.captured[0] as PlatformPoint?; + expect(passedPoint?.x, point.x); + expect(passedPoint?.y, point.y); + }); + + test('Should set the exposure point to null for reset', () async { await camera.setExposurePoint(cameraId, null); - // Assert - expect(channel.log, [ - isMethodCall('setExposurePoint', arguments: { - 'cameraId': cameraId, - 'x': 0.5, - 'y': 0.5, - 'reset': false - }), - isMethodCall('setExposurePoint', arguments: { - 'cameraId': cameraId, - 'x': null, - 'y': null, - 'reset': true - }), - ]); + final VerificationResult verification = + verify(mockApi.setExposurePoint(captureAny)); + final PlatformPoint? passedPoint = + verification.captured[0] as PlatformPoint?; + expect(passedPoint, null); }); test('Should get the min exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMinExposureOffset': 2.0}, - ); + const double stubMinOffset = 2.0; + when(mockApi.getMinExposureOffset()) + .thenAnswer((_) async => stubMinOffset); - // Act final double minExposureOffset = await camera.getMinExposureOffset(cameraId); - // Assert - expect(minExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMinExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); + expect(minExposureOffset, stubMinOffset); }); test('Should get the max exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMaxExposureOffset': 2.0}, - ); + const double stubMaxOffset = 2.0; + when(mockApi.getMaxExposureOffset()) + .thenAnswer((_) async => stubMaxOffset); - // Act final double maxExposureOffset = await camera.getMaxExposureOffset(cameraId); - // Assert - expect(maxExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMaxExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); + expect(maxExposureOffset, stubMaxOffset); }); - test('Should get the exposure offset step size', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getExposureOffsetStepSize': 0.25}, - ); - - // Act + test('Exposure offset step size should always return zero', () async { final double stepSize = await camera.getExposureOffsetStepSize(cameraId); - // Assert - expect(stepSize, 0.25); - expect(channel.log, [ - isMethodCall('getExposureOffsetStepSize', arguments: { - 'cameraId': cameraId, - }), - ]); + expect(stepSize, 0.0); }); test('Should set the exposure offset', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setExposureOffset': 0.6}, - ); - - // Act + const double stubOffset = 0.5; final double actualOffset = await camera.setExposureOffset(cameraId, 0.5); - // Assert - expect(actualOffset, 0.6); - expect(channel.log, [ - isMethodCall('setExposureOffset', arguments: { - 'cameraId': cameraId, - 'offset': 0.5, - }), - ]); + verify(mockApi.setExposureOffset(stubOffset)); + // iOS never adjusts the offset. + expect(actualOffset, stubOffset); }); - test('Should set the focus mode', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setFocusMode': null}, - ); - - // Act + test('Should set the focus mode to auto', () async { await camera.setFocusMode(cameraId, FocusMode.auto); + + verify(mockApi.setFocusMode(PlatformFocusMode.auto)); + }); + + test('Should set the focus mode to locked', () async { await camera.setFocusMode(cameraId, FocusMode.locked); - // Assert - expect(channel.log, [ - isMethodCall('setFocusMode', - arguments: {'cameraId': cameraId, 'mode': 'auto'}), - isMethodCall('setFocusMode', arguments: { - 'cameraId': cameraId, - 'mode': 'locked' - }), - ]); + verify(mockApi.setFocusMode(PlatformFocusMode.locked)); }); - test('Should set the exposure point', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setFocusPoint': null}, - ); + test('Should set the focus point to a value', () async { + const Point point = Point(0.4, 0.6); + await camera.setFocusPoint(cameraId, point); - // Act - await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); + final VerificationResult verification = + verify(mockApi.setFocusPoint(captureAny)); + final PlatformPoint? passedPoint = + verification.captured[0] as PlatformPoint?; + expect(passedPoint?.x, point.x); + expect(passedPoint?.y, point.y); + }); + + test('Should set the focus point to null for reset', () async { await camera.setFocusPoint(cameraId, null); - // Assert - expect(channel.log, [ - isMethodCall('setFocusPoint', arguments: { - 'cameraId': cameraId, - 'x': 0.5, - 'y': 0.5, - 'reset': false - }), - isMethodCall('setFocusPoint', arguments: { - 'cameraId': cameraId, - 'x': null, - 'y': null, - 'reset': true - }), - ]); + final VerificationResult verification = + verify(mockApi.setFocusPoint(captureAny)); + final PlatformPoint? passedPoint = + verification.captured[0] as PlatformPoint?; + expect(passedPoint, null); }); test('Should build a texture widget as preview widget', () async { - // Act final Widget widget = camera.buildPreview(cameraId); - // Act expect(widget is Texture, isTrue); expect((widget as Texture).textureId, cameraId); }); test('Should get the max zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMaxZoomLevel': 10.0}, - ); + const double stubZoomLevel = 10.0; + when(mockApi.getMaxZoomLevel()).thenAnswer((_) async => stubZoomLevel); - // Act final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); - // Assert - expect(maxZoomLevel, 10.0); - expect(channel.log, [ - isMethodCall('getMaxZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); + expect(maxZoomLevel, stubZoomLevel); }); test('Should get the min zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMinZoomLevel': 1.0}, - ); + const double stubZoomLevel = 10.0; + when(mockApi.getMinZoomLevel()).thenAnswer((_) async => stubZoomLevel); - // Act - final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); + final double minZoomLevel = await camera.getMinZoomLevel(cameraId); - // Assert - expect(maxZoomLevel, 1.0); - expect(channel.log, [ - isMethodCall('getMinZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); + expect(minZoomLevel, stubZoomLevel); }); test('Should set the zoom level', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setZoomLevel': null}, - ); + const double zoom = 2.0; - // Act - await camera.setZoomLevel(cameraId, 2.0); + await camera.setZoomLevel(cameraId, zoom); - // Assert - expect(channel.log, [ - isMethodCall('setZoomLevel', - arguments: {'cameraId': cameraId, 'zoom': 2.0}), - ]); + verify(mockApi.setZoomLevel(zoom)); }); test('Should throw CameraException when illegal zoom level is supplied', () async { - // Arrange - MethodChannelMock( - channelName: _channelName, - methods: { - 'setZoomLevel': PlatformException( - code: 'ZOOM_ERROR', - message: 'Illegal zoom error', - ) - }, - ); + const String code = 'ZOOM_ERROR'; + const String message = 'Illegal zoom error'; + when(mockApi.setZoomLevel(any)).thenAnswer( + (_) async => throw PlatformException(code: code, message: message)); - // Act & assert expect( () => camera.setZoomLevel(cameraId, -1.0), throwsA(isA() - .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') + .having((CameraException e) => e.code, 'code', code) .having((CameraException e) => e.description, 'description', - 'Illegal zoom error'))); + message))); }); test('Should lock the capture orientation', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'lockCaptureOrientation': null}, - ); - - // Act await camera.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp); - // Assert - expect(channel.log, [ - isMethodCall('lockCaptureOrientation', arguments: { - 'cameraId': cameraId, - 'orientation': 'portraitUp' - }), - ]); + verify( + mockApi.lockCaptureOrientation(PlatformDeviceOrientation.portraitUp)); }); test('Should unlock the capture orientation', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'unlockCaptureOrientation': null}, - ); - - // Act await camera.unlockCaptureOrientation(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('unlockCaptureOrientation', - arguments: {'cameraId': cameraId}), - ]); + verify(mockApi.unlockCaptureOrientation()); }); test('Should pause the camera preview', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'pausePreview': null}, - ); - - // Act await camera.pausePreview(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('pausePreview', - arguments: {'cameraId': cameraId}), - ]); + verify(mockApi.pausePreview()); }); test('Should resume the camera preview', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'resumePreview': null}, - ); - - // Act await camera.resumePreview(cameraId); - // Assert - expect(channel.log, [ - isMethodCall('resumePreview', - arguments: {'cameraId': cameraId}), - ]); + verify(mockApi.resumePreview()); }); test('Should start streaming', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); - - // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); - // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - ]); + verify(mockApi.startImageStream()); await subscription.cancel(); }); test('Should stop streaming', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); - - // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); await subscription.cancel(); - // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null), - ]); + verify(mockApi.startImageStream()); + verify(mockApi.stopImageStream()); }); test('Should set the ImageFileFormat to heif', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setImageFileFormat': 'heif'}, - ); - - // Act await camera.setImageFileFormat(cameraId, ImageFileFormat.heif); - // Assert - expect(channel.log, [ - isMethodCall('setImageFileFormat', arguments: { - 'cameraId': cameraId, - 'fileFormat': 'heif', - }), - ]); + verify(mockApi.setImageFileFormat(PlatformImageFileFormat.heif)); }); test('Should set the ImageFileFormat to jpeg', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: { - 'setImageFileFormat': 'jpeg', - }, - ); - - // Act await camera.setImageFileFormat(cameraId, ImageFileFormat.jpeg); - // Assert - expect(channel.log, [ - isMethodCall('setImageFileFormat', arguments: { - 'cameraId': cameraId, - 'fileFormat': 'jpeg', - }), - ]); + verify(mockApi.setImageFileFormat(PlatformImageFileFormat.jpeg)); }); }); } diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart index 066815cf266..729b39f7f28 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart @@ -7,6 +7,7 @@ import 'dart:async' as _i3; import 'package:camera_avfoundation/src/messages.g.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -25,10 +26,6 @@ import 'package:mockito/mockito.dart' as _i1; /// /// See the documentation for Mockito's code generation for more information. class MockCameraApi extends _i1.Mock implements _i2.CameraApi { - MockCameraApi() { - _i1.throwOnMissingStub(this); - } - @override _i3.Future> getAvailableCameras() => (super.noSuchMethod( @@ -38,5 +35,348 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { ), returnValue: _i3.Future>.value( <_i2.PlatformCameraDescription?>[]), + returnValueForMissingStub: + _i3.Future>.value( + <_i2.PlatformCameraDescription?>[]), ) as _i3.Future>); + + @override + _i3.Future create( + String? cameraName, + _i2.PlatformMediaSettings? settings, + ) => + (super.noSuchMethod( + Invocation.method( + #create, + [ + cameraName, + settings, + ], + ), + returnValue: _i3.Future.value(0), + returnValueForMissingStub: _i3.Future.value(0), + ) as _i3.Future); + + @override + _i3.Future initialize( + int? cameraId, + _i2.PlatformImageFormatGroup? imageFormat, + ) => + (super.noSuchMethod( + Invocation.method( + #initialize, + [ + cameraId, + imageFormat, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future startImageStream() => (super.noSuchMethod( + Invocation.method( + #startImageStream, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future stopImageStream() => (super.noSuchMethod( + Invocation.method( + #stopImageStream, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future receivedImageStreamData() => (super.noSuchMethod( + Invocation.method( + #receivedImageStreamData, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future dispose(int? cameraId) => (super.noSuchMethod( + Invocation.method( + #dispose, + [cameraId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future lockCaptureOrientation( + _i2.PlatformDeviceOrientation? orientation) => + (super.noSuchMethod( + Invocation.method( + #lockCaptureOrientation, + [orientation], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future unlockCaptureOrientation() => (super.noSuchMethod( + Invocation.method( + #unlockCaptureOrientation, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future takePicture() => (super.noSuchMethod( + Invocation.method( + #takePicture, + [], + ), + returnValue: _i3.Future.value(_i4.dummyValue( + this, + Invocation.method( + #takePicture, + [], + ), + )), + returnValueForMissingStub: + _i3.Future.value(_i4.dummyValue( + this, + Invocation.method( + #takePicture, + [], + ), + )), + ) as _i3.Future); + + @override + _i3.Future prepareForVideoRecording() => (super.noSuchMethod( + Invocation.method( + #prepareForVideoRecording, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future startVideoRecording(bool? enableStream) => + (super.noSuchMethod( + Invocation.method( + #startVideoRecording, + [enableStream], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future stopVideoRecording() => (super.noSuchMethod( + Invocation.method( + #stopVideoRecording, + [], + ), + returnValue: _i3.Future.value(_i4.dummyValue( + this, + Invocation.method( + #stopVideoRecording, + [], + ), + )), + returnValueForMissingStub: + _i3.Future.value(_i4.dummyValue( + this, + Invocation.method( + #stopVideoRecording, + [], + ), + )), + ) as _i3.Future); + + @override + _i3.Future pauseVideoRecording() => (super.noSuchMethod( + Invocation.method( + #pauseVideoRecording, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future resumeVideoRecording() => (super.noSuchMethod( + Invocation.method( + #resumeVideoRecording, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setFlashMode(_i2.PlatformFlashMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setFlashMode, + [mode], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setExposureMode(_i2.PlatformExposureMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setExposureMode, + [mode], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setExposurePoint(_i2.PlatformPoint? point) => + (super.noSuchMethod( + Invocation.method( + #setExposurePoint, + [point], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future getMinExposureOffset() => (super.noSuchMethod( + Invocation.method( + #getMinExposureOffset, + [], + ), + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); + + @override + _i3.Future getMaxExposureOffset() => (super.noSuchMethod( + Invocation.method( + #getMaxExposureOffset, + [], + ), + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); + + @override + _i3.Future setExposureOffset(double? offset) => (super.noSuchMethod( + Invocation.method( + #setExposureOffset, + [offset], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setFocusMode(_i2.PlatformFocusMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setFocusMode, + [mode], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setFocusPoint(_i2.PlatformPoint? point) => + (super.noSuchMethod( + Invocation.method( + #setFocusPoint, + [point], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future getMinZoomLevel() => (super.noSuchMethod( + Invocation.method( + #getMinZoomLevel, + [], + ), + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); + + @override + _i3.Future getMaxZoomLevel() => (super.noSuchMethod( + Invocation.method( + #getMaxZoomLevel, + [], + ), + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); + + @override + _i3.Future setZoomLevel(double? zoom) => (super.noSuchMethod( + Invocation.method( + #setZoomLevel, + [zoom], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future pausePreview() => (super.noSuchMethod( + Invocation.method( + #pausePreview, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future resumePreview() => (super.noSuchMethod( + Invocation.method( + #resumePreview, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updateDescriptionWhileRecording(String? cameraName) => + (super.noSuchMethod( + Invocation.method( + #updateDescriptionWhileRecording, + [cameraName], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setImageFileFormat(_i2.PlatformImageFileFormat? format) => + (super.noSuchMethod( + Invocation.method( + #setImageFileFormat, + [format], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } diff --git a/packages/camera/camera_avfoundation/test/method_channel_mock.dart b/packages/camera/camera_avfoundation/test/method_channel_mock.dart deleted file mode 100644 index a7362d0e049..00000000000 --- a/packages/camera/camera_avfoundation/test/method_channel_mock.dart +++ /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. - -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MethodChannelMock { - MethodChannelMock({ - required String channelName, - this.delay, - required this.methods, - }) : methodChannel = MethodChannel(channelName) { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, _handler); - } - - final Duration? delay; - final MethodChannel methodChannel; - final Map methods; - final List log = []; - - Future _handler(MethodCall methodCall) async { - log.add(methodCall); - - if (!methods.containsKey(methodCall.method)) { - throw MissingPluginException('No implementation found for method ' - '${methodCall.method} on channel ${methodChannel.name}'); - } - - return Future.delayed(delay ?? Duration.zero, () { - final dynamic result = methods[methodCall.method]; - if (result is Exception) { - throw result; - } - - return Future.value(result); - }); - } -} diff --git a/packages/camera/camera_avfoundation/test/utils_test.dart b/packages/camera/camera_avfoundation/test/utils_test.dart index 53fc72b43dc..0e45e39c9e2 100644 --- a/packages/camera/camera_avfoundation/test/utils_test.dart +++ b/packages/camera/camera_avfoundation/test/utils_test.dart @@ -27,13 +27,13 @@ void main() { test('serializeDeviceOrientation() should serialize correctly', () { expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), - 'portraitUp'); + PlatformDeviceOrientation.portraitUp); expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), - 'portraitDown'); + PlatformDeviceOrientation.portraitDown); expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), - 'landscapeRight'); + PlatformDeviceOrientation.landscapeRight); expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), - 'landscapeLeft'); + PlatformDeviceOrientation.landscapeLeft); }); test('deviceOrientationFromPlatform() should convert correctly', () { diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 836b73586df..a51a5a0d491 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.7.4 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index e139e2062db..50a6a9b4af5 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.7.4 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: cross_file: ^0.3.1 diff --git a/packages/camera/camera_web/CHANGELOG.md b/packages/camera/camera_web/CHANGELOG.md index 0b6202b05b9..9788191de35 100644 --- a/packages/camera/camera_web/CHANGELOG.md +++ b/packages/camera/camera_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 0.3.3 * Adds support to control video FPS and bitrate. See `CameraController.withSettings`. diff --git a/packages/camera/camera_web/README.md b/packages/camera/camera_web/README.md index 6ebf24e9bb5..1d20e9a88fc 100644 --- a/packages/camera/camera_web/README.md +++ b/packages/camera/camera_web/README.md @@ -8,7 +8,7 @@ The web implementation of [`camera`][camera]. ### Depend on the package -This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +This package is [endorsed](https://flutter.dev/to/endorsed-federated-plugin), which means you can simply use `camera` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. diff --git a/packages/camera/camera_web/example/README.md b/packages/camera/camera_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/camera/camera_web/example/README.md +++ b/packages/camera/camera_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/camera/camera_web/example/integration_test/camera_error_code_test.dart b/packages/camera/camera_web/example/integration_test/camera_error_code_test.dart index e89018f7c51..6bd86b0fe8e 100644 --- a/packages/camera/camera_web/example/integration_test/camera_error_code_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_error_code_test.dart @@ -4,6 +4,7 @@ import 'dart:html'; +// ignore: implementation_imports import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/packages/camera/camera_web/example/integration_test/camera_metadata_test.dart b/packages/camera/camera_web/example/integration_test/camera_metadata_test.dart index 07252be5c7a..fa4547726ce 100644 --- a/packages/camera/camera_web/example/integration_test/camera_metadata_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_metadata_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore: implementation_imports import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/packages/camera/camera_web/example/integration_test/camera_options_test.dart b/packages/camera/camera_web/example/integration_test/camera_options_test.dart index 6619ff41e03..7dd25e37556 100644 --- a/packages/camera/camera_web/example/integration_test/camera_options_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_options_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore: implementation_imports import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/packages/camera/camera_web/example/integration_test/camera_service_test.dart b/packages/camera/camera_web/example/integration_test/camera_service_test.dart index 09a18c01158..fb2279a0942 100644 --- a/packages/camera/camera_web/example/integration_test/camera_service_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_service_test.dart @@ -6,6 +6,7 @@ import 'dart:html'; import 'dart:js_util' as js_util; import 'package:camera_platform_interface/camera_platform_interface.dart'; +// ignore_for_file: implementation_imports import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/shims/dart_js_util.dart'; diff --git a/packages/camera/camera_web/example/integration_test/camera_test.dart b/packages/camera/camera_web/example/integration_test/camera_test.dart index 705d7750e1a..412ce7148bc 100644 --- a/packages/camera/camera_web/example/integration_test/camera_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_test.dart @@ -8,6 +8,7 @@ import 'dart:ui'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; +// ignore_for_file: implementation_imports import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/types/types.dart'; diff --git a/packages/camera/camera_web/example/integration_test/camera_web_exception_test.dart b/packages/camera/camera_web/example/integration_test/camera_web_exception_test.dart index fcb54da1aed..2647fe7f2b3 100644 --- a/packages/camera/camera_web/example/integration_test/camera_web_exception_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_web_exception_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore: implementation_imports import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/packages/camera/camera_web/example/integration_test/camera_web_test.dart b/packages/camera/camera_web/example/integration_test/camera_web_test.dart index d31855f087e..cfce6c4f5a9 100644 --- a/packages/camera/camera_web/example/integration_test/camera_web_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_web_test.dart @@ -8,6 +8,7 @@ import 'dart:html'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_web/camera_web.dart'; +// ignore_for_file: implementation_imports import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/types/types.dart'; diff --git a/packages/camera/camera_web/example/integration_test/helpers/mocks.dart b/packages/camera/camera_web/example/integration_test/helpers/mocks.dart index 855ef2b9c58..d1fbdd574ba 100644 --- a/packages/camera/camera_web/example/integration_test/helpers/mocks.dart +++ b/packages/camera/camera_web/example/integration_test/helpers/mocks.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:html'; import 'dart:ui'; +// ignore_for_file: implementation_imports import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/shims/dart_js_util.dart'; diff --git a/packages/camera/camera_web/example/integration_test/zoom_level_capability_test.dart b/packages/camera/camera_web/example/integration_test/zoom_level_capability_test.dart index 8614cd95880..d93b42690e5 100644 --- a/packages/camera/camera_web/example/integration_test/zoom_level_capability_test.dart +++ b/packages/camera/camera_web/example/integration_test/zoom_level_capability_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore: implementation_imports import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/packages/camera/camera_web/example/pubspec.yaml b/packages/camera/camera_web/example/pubspec.yaml index e204c437fb4..5cf64e1c5ce 100644 --- a/packages/camera/camera_web/example/pubspec.yaml +++ b/packages/camera/camera_web/example/pubspec.yaml @@ -2,8 +2,8 @@ name: camera_web_integration_tests publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: camera_platform_interface: ^2.6.0 diff --git a/packages/camera/camera_web/pubspec.yaml b/packages/camera/camera_web/pubspec.yaml index 2cd0f4f6b26..8e47d50a09a 100644 --- a/packages/camera/camera_web/pubspec.yaml +++ b/packages/camera/camera_web/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.3.3 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index 01e8358fd18..9efbd0fd10f 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 0.2.2 * Adds support to control video FPS and bitrate. See `CameraController.withSettings`. diff --git a/packages/camera/camera_windows/README.md b/packages/camera/camera_windows/README.md index 4b66ad3dfe3..429e1f6e47f 100644 --- a/packages/camera/camera_windows/README.md +++ b/packages/camera/camera_windows/README.md @@ -61,7 +61,7 @@ disposing of the camera is the only way to reset the situation. [camera]: https://pub.dev/packages/camera -[endorsed-federated-plugin]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[endorsed-federated-plugin]: https://flutter.dev/to/endorsed-federated-plugin [install]: https://pub.dev/packages/camera_windows/install [camera-control-issue]: https://github.com/flutter/flutter/issues/97537 [device-orientation-issue]: https://github.com/flutter/flutter/issues/97540 diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 7a0853acce9..6e94a03e4ae 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the camera_windows plugin. publish_to: 'none' environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: camera_platform_interface: ^2.6.0 diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index de2f695c754..6fc2e8a9c7d 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.2.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index 2c4e4939615..1ca705da8a3 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 0.3.4+1 * Removes a few deprecated API usages. diff --git a/packages/cross_file/example/pubspec.yaml b/packages/cross_file/example/pubspec.yaml index 20c49e7b419..cfcc8e3a9a5 100644 --- a/packages/cross_file/example/pubspec.yaml +++ b/packages/cross_file/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use cross files. publish_to: none environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: cross_file: diff --git a/packages/css_colors/CHANGELOG.md b/packages/css_colors/CHANGELOG.md index 141ad09236e..abcb748b947 100644 --- a/packages/css_colors/CHANGELOG.md +++ b/packages/css_colors/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 1.1.5 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/css_colors/pubspec.yaml b/packages/css_colors/pubspec.yaml index bdccb399303..962c5279a94 100644 --- a/packages/css_colors/pubspec.yaml +++ b/packages/css_colors/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.1.5 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/dynamic_layouts/.gitignore b/packages/dynamic_layouts/.gitignore deleted file mode 100644 index 96486fd9302..00000000000 --- a/packages/dynamic_layouts/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock -**/doc/api/ -.dart_tool/ -.packages -build/ diff --git a/packages/dynamic_layouts/.metadata b/packages/dynamic_layouts/.metadata deleted file mode 100644 index 1ed935450ae..00000000000 --- a/packages/dynamic_layouts/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: fe0beec6d44c689837a46cd4962a998d77e332ff - channel: master - -project_type: package diff --git a/packages/dynamic_layouts/CHANGELOG.md b/packages/dynamic_layouts/CHANGELOG.md deleted file mode 100644 index 19e3b118c04..00000000000 --- a/packages/dynamic_layouts/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -## NEXT - -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. - -## 0.0.1+1 - -* Removes obsolete null checks on non-nullable values. -* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. -* Aligns Dart and Flutter SDK constraints. -* Updates minimum Flutter version to 3.0. - -## 0.0.1 - -* Initial release diff --git a/packages/dynamic_layouts/LICENSE b/packages/dynamic_layouts/LICENSE deleted file mode 100644 index c6823b81eb8..00000000000 --- a/packages/dynamic_layouts/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/dynamic_layouts/README.md b/packages/dynamic_layouts/README.md deleted file mode 100644 index 03acc43bdba..00000000000 --- a/packages/dynamic_layouts/README.md +++ /dev/null @@ -1,78 +0,0 @@ -A package that provides two dynamic grid layouts: wrap and staggered. - -## Features -This package provides support for multi sized tiles and different layouts. -Currently the layouts that are implemented in this package are `Stagger` and -`Wrap`. - -### Stagger Features - -`DynamicGridView` is a subclass of `GridView` and gives access -to the `SliverGridDelegate`s that are already implemented in the Flutter -Framework. Some `SliverGridDelegate`s are `SliverGridDelegateWithMaxCrossAxisExtent` and -`SliverGridDelegateWithFixedCrossAxisCount`. This layout can be used with -`DynamicGridView.stagger`. - -### Wrap Features - -The Wrap layout is able to do runs of different widgets and adapt accordingly with -the sizes of the children. It can leave spacing with `mainAxisSpacing` and -`crossAxisSpacing`. - -Having different sizes in only one of the axis is possible by -changing the values of `childCrossAxisExtent` and `childMainAxisExtent`. These -values by default are set to have loose constraints, but by giving `childCrossAxisExtent` a specific value like -100 pixels, it will make all of the children 100 pixels in the main axis. -This layout can be used with `DynamicGridView.wrap` and with -`DynamicGridView.builder` and `SliverGridDelegateWithWrapping` as the delegate. - -## Getting started - -### Depend on it - -Run this command with Flutter: -```sh -$ flutter pub add dynamic_layouts -``` -### Import it - -Now in your Dart code, you can use: - -```sh -import 'package:dynamic_layouts/dynamic_layouts.dart'; -``` -## Usage - -Use `DynamicGridView`s to access these layouts. -`DynamicGridView` has some constructors that use `SliverChildListDelegate` like -`.wrap` and `.stagger`. For a more efficient option that uses `SliverChildBuilderDelegate` use -`.builder`, it works the same as `GridView.builder`. - -### Wrap - -The following are simple examples of how to use `DynamicGridView.wrap`. - -The following example uses `DynamicGridView.builder` with -`SliverGridDelegateWithWrapping`. - -By using `childCrossAxisExtent` and `childMainAxisExtent` the main axis -can be limited to have a specific size and the other can be set to loose -constraints. - - -### Stagger - -The `Stagger` layout can be used with the constructor -`DynamicGridView.stagger` and still use the delegates from `GridView` -like `SliverGridDelegateWithMaxCrossAxisExtent` and -`SliverGridDelegateWithFixedCrossAxisCount`. - -## Additional information - -The staggered layout is similar to Android's [StaggeredGridLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager), while the wrap layout -emulates iOS' [UICollectionView](https://developer.apple.com/documentation/uikit/uicollectionview). - -The inner functionality of this package is exposed, meaning that other dynamic layouts -can be created on top of it and added to the collection. If you want to contribute to -this package, you can open a pull request in [Flutter Packages](https://github.com/flutter/packages) -and add the tag "p: dynamic_layouts". diff --git a/packages/dynamic_layouts/example/.gitignore b/packages/dynamic_layouts/example/.gitignore deleted file mode 100644 index a8e938c0839..00000000000 --- a/packages/dynamic_layouts/example/.gitignore +++ /dev/null @@ -1,47 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/packages/dynamic_layouts/example/.metadata b/packages/dynamic_layouts/example/.metadata deleted file mode 100644 index ed0b5185fb8..00000000000 --- a/packages/dynamic_layouts/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - platform: android - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - platform: ios - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - platform: linux - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - platform: macos - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - platform: web - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - platform: windows - create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/dynamic_layouts/example/.pluginToolsConfig.yaml b/packages/dynamic_layouts/example/.pluginToolsConfig.yaml deleted file mode 100644 index 3b6017b7609..00000000000 --- a/packages/dynamic_layouts/example/.pluginToolsConfig.yaml +++ /dev/null @@ -1,4 +0,0 @@ -buildFlags: - _pluginToolsConfigGlobalKey: - - "--no-tree-shake-icons" - - "--dart-define=buildmode=testing" diff --git a/packages/dynamic_layouts/example/README.md b/packages/dynamic_layouts/example/README.md deleted file mode 100644 index 8017af06b81..00000000000 --- a/packages/dynamic_layouts/example/README.md +++ /dev/null @@ -1 +0,0 @@ -# An example app for staggered & wrap layout diff --git a/packages/dynamic_layouts/example/android/.gitignore b/packages/dynamic_layouts/example/android/.gitignore deleted file mode 100644 index 6f568019d3c..00000000000 --- a/packages/dynamic_layouts/example/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks diff --git a/packages/dynamic_layouts/example/android/app/build.gradle b/packages/dynamic_layouts/example/android/app/build.gradle deleted file mode 100644 index f2293865f2f..00000000000 --- a/packages/dynamic_layouts/example/android/app/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - namespace 'dev.flutter.packages.animations.example' - compileSdk flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } - namespace 'com.example.example' -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/dynamic_layouts/example/android/app/src/debug/AndroidManifest.xml b/packages/dynamic_layouts/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f6981d5d..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/dynamic_layouts/example/android/app/src/main/AndroidManifest.xml b/packages/dynamic_layouts/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index e76d500db84..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/dynamic_layouts/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f3f6a..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/drawable/launch_background.xml b/packages/dynamic_layouts/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f8842..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0..00000000000 Binary files a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8..00000000000 Binary files a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d4391482b..00000000000 Binary files a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7..00000000000 Binary files a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb..00000000000 Binary files a/packages/dynamic_layouts/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/values-night/styles.xml b/packages/dynamic_layouts/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be745f..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/dynamic_layouts/example/android/app/src/main/res/values/styles.xml b/packages/dynamic_layouts/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88056e..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/dynamic_layouts/example/android/app/src/profile/AndroidManifest.xml b/packages/dynamic_layouts/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981d5d..00000000000 --- a/packages/dynamic_layouts/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/dynamic_layouts/example/android/build.gradle b/packages/dynamic_layouts/example/android/build.gradle deleted file mode 100644 index 582d60a2faa..00000000000 --- a/packages/dynamic_layouts/example/android/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. - def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' - if (System.getenv().containsKey(artifactRepoKey)) { - println "Using artifact hub" - maven { url System.getenv(artifactRepoKey) } - } - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/packages/dynamic_layouts/example/android/gradle.properties b/packages/dynamic_layouts/example/android/gradle.properties deleted file mode 100644 index 94adc3a3f97..00000000000 --- a/packages/dynamic_layouts/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/dynamic_layouts/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/dynamic_layouts/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index aeaff6f869f..00000000000 --- a/packages/dynamic_layouts/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/packages/dynamic_layouts/example/android/settings.gradle b/packages/dynamic_layouts/example/android/settings.gradle deleted file mode 100644 index 3a97314b38c..00000000000 --- a/packages/dynamic_layouts/example/android/settings.gradle +++ /dev/null @@ -1,24 +0,0 @@ -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" - -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. -buildscript { - repositories { - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" - } -} -apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" diff --git a/packages/dynamic_layouts/example/ios/.gitignore b/packages/dynamic_layouts/example/ios/.gitignore deleted file mode 100644 index 7a7f9873ad7..00000000000 --- a/packages/dynamic_layouts/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/dynamic_layouts/example/ios/Flutter/Debug.xcconfig b/packages/dynamic_layouts/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee85b8..00000000000 --- a/packages/dynamic_layouts/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/dynamic_layouts/example/ios/Flutter/Release.xcconfig b/packages/dynamic_layouts/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee85b8..00000000000 --- a/packages/dynamic_layouts/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5ea1..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/dynamic_layouts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index a6b826db27d..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/dynamic_layouts/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/dynamic_layouts/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16ed0..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/dynamic_layouts/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/dynamic_layouts/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/dynamic_layouts/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/dynamic_layouts/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5ea1..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/dynamic_layouts/example/ios/Runner/AppDelegate.swift b/packages/dynamic_layouts/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index d83c0ff0bee..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,17 +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 Flutter -import UIKit - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e..00000000000 Binary files a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/ios/Runner/Info.plist b/packages/dynamic_layouts/example/ios/Runner/Info.plist deleted file mode 100644 index 5458fc4188b..00000000000 --- a/packages/dynamic_layouts/example/ios/Runner/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Example - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - diff --git a/packages/dynamic_layouts/example/lib/main.dart b/packages/dynamic_layouts/example/lib/main.dart deleted file mode 100644 index 18a2c20e407..00000000000 --- a/packages/dynamic_layouts/example/lib/main.dart +++ /dev/null @@ -1,70 +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 'package:flutter/material.dart'; - -import 'staggered_layout_example.dart'; -import 'wrap_layout_example.dart'; - -void main() { - runApp(const MyApp()); -} - -/// Main example -class MyApp extends StatelessWidget { - /// Main example constructor. - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(), - ); - } -} - -/// The home page -class MyHomePage extends StatelessWidget { - /// The home page constructor. - const MyHomePage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Demo App'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => const WrapExample(), - ), - ), - child: const Text('Wrap Demo'), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => const StaggeredExample(), - ), - ), - child: const Text('Staggered Demo'), - ), - ], - ), - ), - ); - } -} diff --git a/packages/dynamic_layouts/example/lib/staggered_layout_example.dart b/packages/dynamic_layouts/example/lib/staggered_layout_example.dart deleted file mode 100644 index 95b30203e24..00000000000 --- a/packages/dynamic_layouts/example/lib/staggered_layout_example.dart +++ /dev/null @@ -1,87 +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 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/material.dart'; - -void main() { - runApp(const StaggeredExample()); -} - -/// A staggered layout example. Clicking the upper-right button will change -/// between a grid with a fixed cross axis count and one with a main axis -/// extent. -class StaggeredExample extends StatefulWidget { - /// Creates a [StaggeredExample]. - const StaggeredExample({super.key}); - - @override - State createState() => _StaggeredExampleState(); -} - -class _StaggeredExampleState extends State { - final List children = List.generate( - 50, - (int index) => _DynamicSizedTile(index: index), - ); - - bool fixedCrossAxisCount = true; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Staggered Layout Example'), - actions: [ - Padding( - padding: const EdgeInsets.only(right: 50.0), - child: TextButton( - onPressed: () { - setState(() { - fixedCrossAxisCount = !fixedCrossAxisCount; - }); - }, - child: Text( - fixedCrossAxisCount ? 'FIXED' : 'MAX', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - setState(() { - children.add(_DynamicSizedTile(index: children.length)); - }); - }, - child: const Icon(Icons.plus_one), - ), - body: fixedCrossAxisCount - ? DynamicGridView.staggered( - crossAxisCount: 4, - children: [...children], - ) - : DynamicGridView.staggered( - maxCrossAxisExtent: 100, - children: [...children], - ), - ); - } -} - -class _DynamicSizedTile extends StatelessWidget { - const _DynamicSizedTile({required this.index}); - - final int index; - - @override - Widget build(BuildContext context) { - return Container( - height: index % 3 * 50 + 20, - color: Colors.amber[(index % 8 + 1) * 100], - child: Text('Index $index'), - ); - } -} diff --git a/packages/dynamic_layouts/example/lib/wrap_layout_example.dart b/packages/dynamic_layouts/example/lib/wrap_layout_example.dart deleted file mode 100644 index 06ee311bac6..00000000000 --- a/packages/dynamic_layouts/example/lib/wrap_layout_example.dart +++ /dev/null @@ -1,37 +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 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/material.dart'; - -/// The wrap example -class WrapExample extends StatelessWidget { - /// The constructor for the wrap example - const WrapExample({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Wrap demo'), - ), - body: DynamicGridView.builder( - gridDelegate: const SliverGridDelegateWithWrapping(), - itemCount: 20, - itemBuilder: (BuildContext context, int index) { - return Container( - height: index.isEven ? index % 7 * 50 + 150 : index % 4 * 50 + 100, - width: index.isEven ? index % 5 * 20 + 40 : index % 3 * 50 + 100, - color: index.isEven - ? Colors.red[(index % 7 + 1) * 100] - : Colors.blue[(index % 7 + 1) * 100], - child: Center( - child: Text('Index $index'), - ), - ); - }, - ), - ); - } -} diff --git a/packages/dynamic_layouts/example/linux/.gitignore b/packages/dynamic_layouts/example/linux/.gitignore deleted file mode 100644 index d3896c98444..00000000000 --- a/packages/dynamic_layouts/example/linux/.gitignore +++ /dev/null @@ -1 +0,0 @@ -flutter/ephemeral diff --git a/packages/dynamic_layouts/example/linux/CMakeLists.txt b/packages/dynamic_layouts/example/linux/CMakeLists.txt deleted file mode 100644 index 74c66dd4463..00000000000 --- a/packages/dynamic_layouts/example/linux/CMakeLists.txt +++ /dev/null @@ -1,138 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.10) -project(runner LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "example") -# The unique GTK application identifier for this application. See: -# https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(SET CMP0063 NEW) - -# Load bundled libraries from the lib/ directory relative to the binary. -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endif() - -# Define build configuration options. -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") -endif() - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_14) - target_compile_options(${TARGET} PRIVATE -Wall -Werror) - target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") - target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) - -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Define the application target. To change its name, change BINARY_NAME above, -# not the value here, or `flutter run` will no longer work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) - -# Only the install-generated bundle's copy of the executable will launch -# correctly, since the resources must in the right relative locations. To avoid -# people trying to run the unbundled copy, put it in a subdirectory instead of -# the default top-level location. -set_target_properties(${BINARY_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" -) - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# By default, "installing" just makes a relocatable bundle in the build -# directory. -set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -# Start with a clean build bundle directory every time. -install(CODE " - file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") - " COMPONENT Runtime) - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endforeach(bundled_library) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() diff --git a/packages/dynamic_layouts/example/linux/flutter/CMakeLists.txt b/packages/dynamic_layouts/example/linux/flutter/CMakeLists.txt deleted file mode 100644 index d5bd01648a9..00000000000 --- a/packages/dynamic_layouts/example/linux/flutter/CMakeLists.txt +++ /dev/null @@ -1,88 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.10) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. - -# Serves the same purpose as list(TRANSFORM ... PREPEND ...), -# which isn't available in 3.10. -function(list_prepend LIST_NAME PREFIX) - set(NEW_LIST "") - foreach(element ${${LIST_NAME}}) - list(APPEND NEW_LIST "${PREFIX}${element}") - endforeach(element) - set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) -endfunction() - -# === Flutter Library === -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) -pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) - -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "fl_basic_message_channel.h" - "fl_binary_codec.h" - "fl_binary_messenger.h" - "fl_dart_project.h" - "fl_engine.h" - "fl_json_message_codec.h" - "fl_json_method_codec.h" - "fl_message_codec.h" - "fl_method_call.h" - "fl_method_channel.h" - "fl_method_codec.h" - "fl_method_response.h" - "fl_plugin_registrar.h" - "fl_plugin_registry.h" - "fl_standard_message_codec.h" - "fl_standard_method_codec.h" - "fl_string_codec.h" - "fl_value.h" - "fl_view.h" - "flutter_linux.h" -) -list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") -target_link_libraries(flutter INTERFACE - PkgConfig::GTK - PkgConfig::GLIB - PkgConfig::GIO -) -add_dependencies(flutter flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CMAKE_CURRENT_BINARY_DIR}/_phony_ - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" - ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} -) diff --git a/packages/dynamic_layouts/example/linux/flutter/generated_plugins.cmake b/packages/dynamic_layouts/example/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb..00000000000 --- a/packages/dynamic_layouts/example/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/packages/dynamic_layouts/example/linux/main.cc b/packages/dynamic_layouts/example/linux/main.cc deleted file mode 100644 index 95e17142fc0..00000000000 --- a/packages/dynamic_layouts/example/linux/main.cc +++ /dev/null @@ -1,9 +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. -#include "my_application.h" - -int main(int argc, char** argv) { - g_autoptr(MyApplication) app = my_application_new(); - return g_application_run(G_APPLICATION(app), argc, argv); -} diff --git a/packages/dynamic_layouts/example/linux/my_application.cc b/packages/dynamic_layouts/example/linux/my_application.cc deleted file mode 100644 index 3fba4a13441..00000000000 --- a/packages/dynamic_layouts/example/linux/my_application.cc +++ /dev/null @@ -1,110 +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. -#include "my_application.h" - -#include -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#include "flutter/generated_plugin_registrant.h" - -struct _MyApplication { - GtkApplication parent_instance; - char** dart_entrypoint_arguments; -}; - -G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) - -// Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = - GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); - - // Use a header bar when running in GNOME as this is the common style used - // by applications and is the setup most users will be using (e.g. Ubuntu - // desktop). - // If running on X and not using GNOME then just use a traditional title bar - // in case the window manager does more exotic layout, e.g. tiling. - // If running on Wayland assume the header bar will work (may need changing - // if future cases occur). - gboolean use_header_bar = TRUE; -#ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; - } - } -#endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); - gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "example"); - gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "example"); - } - - gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); - - g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments( - project, self->dart_entrypoint_arguments); - - FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - - fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus(GTK_WIDGET(view)); -} - -// Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, - gchar*** arguments, - int* exit_status) { - MyApplication* self = MY_APPLICATION(application); - // Strip out the first argument as it is the binary name. - self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); - - g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; - } - - g_application_activate(application); - *exit_status = 0; - - return TRUE; -} - -// Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); - g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); - G_OBJECT_CLASS(my_application_parent_class)->dispose(object); -} - -static void my_application_class_init(MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = - my_application_local_command_line; - G_OBJECT_CLASS(klass)->dispose = my_application_dispose; -} - -static void my_application_init(MyApplication* self) {} - -MyApplication* my_application_new() { - return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, "flags", - G_APPLICATION_NON_UNIQUE, nullptr)); -} diff --git a/packages/dynamic_layouts/example/linux/my_application.h b/packages/dynamic_layouts/example/linux/my_application.h deleted file mode 100644 index df30cf703a7..00000000000 --- a/packages/dynamic_layouts/example/linux/my_application.h +++ /dev/null @@ -1,21 +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. -#ifndef FLUTTER_MY_APPLICATION_H_ -#define FLUTTER_MY_APPLICATION_H_ - -#include - -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, - GtkApplication) - -/** - * my_application_new: - * - * Creates a new Flutter-based application. - * - * Returns: a new #MyApplication. - */ -MyApplication* my_application_new(); - -#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/dynamic_layouts/example/macos/.gitignore b/packages/dynamic_layouts/example/macos/.gitignore deleted file mode 100644 index 746adbb6b9e..00000000000 --- a/packages/dynamic_layouts/example/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/packages/dynamic_layouts/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/dynamic_layouts/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index c2efd0b608b..00000000000 --- a/packages/dynamic_layouts/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/dynamic_layouts/example/macos/Flutter/Flutter-Release.xcconfig b/packages/dynamic_layouts/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index c2efd0b608b..00000000000 --- a/packages/dynamic_layouts/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/dynamic_layouts/example/macos/Runner.xcodeproj/project.pbxproj b/packages/dynamic_layouts/example/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index d9333e4704c..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,573 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* example.app */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/packages/dynamic_layouts/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/dynamic_layouts/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/dynamic_layouts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/dynamic_layouts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index fb7259e1778..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/dynamic_layouts/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/dynamic_layouts/example/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16ed0..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/dynamic_layouts/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/dynamic_layouts/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/dynamic_layouts/example/macos/Runner/AppDelegate.swift b/packages/dynamic_layouts/example/macos/Runner/AppDelegate.swift deleted file mode 100644 index 805f1339ed8..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,12 +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 Cocoa -import FlutterMacOS - -@NSApplicationMain -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } -} diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a2ec33f19f1..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 3c4935a7ca8..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100644 index ed4cc164216..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100644 index 483be613897..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png deleted file mode 100644 index bcbf36df2f2..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png deleted file mode 100644 index 9c0a6528647..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png deleted file mode 100644 index e71a726136a..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100644 index 8a31fe2dd3f..00000000000 Binary files a/packages/dynamic_layouts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/dynamic_layouts/example/macos/Runner/Base.lproj/MainMenu.xib deleted file mode 100644 index 80e867a4e06..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/dynamic_layouts/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/dynamic_layouts/example/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index 8b42559e875..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. diff --git a/packages/dynamic_layouts/example/macos/Runner/Configs/Debug.xcconfig b/packages/dynamic_layouts/example/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd9464f..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/packages/dynamic_layouts/example/macos/Runner/Configs/Release.xcconfig b/packages/dynamic_layouts/example/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f49561c..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/packages/dynamic_layouts/example/macos/Runner/Configs/Warnings.xcconfig b/packages/dynamic_layouts/example/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf4780b..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/dynamic_layouts/example/macos/Runner/DebugProfile.entitlements b/packages/dynamic_layouts/example/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index dddb8a30c85..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/packages/dynamic_layouts/example/macos/Runner/Info.plist b/packages/dynamic_layouts/example/macos/Runner/Info.plist deleted file mode 100644 index 4789daa6a44..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/packages/dynamic_layouts/example/macos/Runner/MainFlutterWindow.swift b/packages/dynamic_layouts/example/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index 9e87efa5a14..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,18 +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 Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/packages/dynamic_layouts/example/macos/Runner/Release.entitlements b/packages/dynamic_layouts/example/macos/Runner/Release.entitlements deleted file mode 100644 index 852fa1a4728..00000000000 --- a/packages/dynamic_layouts/example/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/packages/dynamic_layouts/example/pubspec.yaml b/packages/dynamic_layouts/example/pubspec.yaml deleted file mode 100644 index 7d4558165e7..00000000000 --- a/packages/dynamic_layouts/example/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: example -description: A new Flutter project. - -publish_to: 'none' - -version: 1.0.0+1 - -environment: - sdk: ^3.1.0 - -dependencies: - cupertino_icons: ^1.0.5 - dynamic_layouts: - path: ../ - flutter: - sdk: flutter - -dev_dependencies: - flutter_lints: ^2.0.0 - flutter_test: - sdk: flutter - -flutter: - uses-material-design: true diff --git a/packages/dynamic_layouts/example/test/staggered_example_test.dart b/packages/dynamic_layouts/example/test/staggered_example_test.dart deleted file mode 100644 index c43b4ff413d..00000000000 --- a/packages/dynamic_layouts/example/test/staggered_example_test.dart +++ /dev/null @@ -1,33 +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 'package:example/staggered_layout_example.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('StaggeredExample lays out children correctly', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(400, 200); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - const MaterialApp( - home: StaggeredExample(), - ), - ); - await tester.pumpAndSettle(); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), const Offset(0.0, 56.0)); - expect(find.text('Index 8'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 8')), const Offset(100.0, 146.0)); - expect(find.text('Index 10'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 10')), - const Offset(200.0, 196.0), - ); - }); -} diff --git a/packages/dynamic_layouts/example/test/wrap_example_test.dart b/packages/dynamic_layouts/example/test/wrap_example_test.dart deleted file mode 100644 index 1179ea29433..00000000000 --- a/packages/dynamic_layouts/example/test/wrap_example_test.dart +++ /dev/null @@ -1,87 +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 'package:example/wrap_layout_example.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Check wrap layout', (WidgetTester tester) async { - const MaterialApp app = MaterialApp( - home: WrapExample(), - ); - await tester.pumpWidget(app); - await tester.pumpAndSettle(); - - // Validate which children are laid out. - for (int i = 0; i <= 12; i++) { - expect(find.text('Index $i'), findsOneWidget); - } - for (int i = 13; i < 19; i++) { - expect(find.text('Index $i'), findsNothing); - } - - // Validate with the position of the box, not the text. - Finder getContainer(String text) { - return find.ancestor( - of: find.text(text), - matching: find.byType(Container), - ); - } - - // Validate layout position. - expect( - tester.getTopLeft(getContainer('Index 0')), - const Offset(0.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 1')), - const Offset(40.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 2')), - const Offset(190.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 3')), - const Offset(270.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 4')), - const Offset(370.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 5')), - const Offset(490.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 6')), - const Offset(690.0, 56.0), - ); - expect( - tester.getTopLeft(getContainer('Index 7')), - const Offset(0.0, 506.0), - ); - expect( - tester.getTopLeft(getContainer('Index 8')), - const Offset(150.0, 506.0), - ); - expect( - tester.getTopLeft(getContainer('Index 9')), - const Offset(250.0, 506.0), - ); - expect( - tester.getTopLeft(getContainer('Index 10')), - const Offset(350.0, 506.0), - ); - expect( - tester.getTopLeft(getContainer('Index 11')), - const Offset(390.0, 506.0), - ); - expect( - tester.getTopLeft(getContainer('Index 12')), - const Offset(590.0, 506.0), - ); - }); -} diff --git a/packages/dynamic_layouts/example/web/favicon.png b/packages/dynamic_layouts/example/web/favicon.png deleted file mode 100644 index 8aaa46ac1ae..00000000000 Binary files a/packages/dynamic_layouts/example/web/favicon.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/web/icons/Icon-192.png b/packages/dynamic_layouts/example/web/icons/Icon-192.png deleted file mode 100644 index b749bfef074..00000000000 Binary files a/packages/dynamic_layouts/example/web/icons/Icon-192.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/web/icons/Icon-512.png b/packages/dynamic_layouts/example/web/icons/Icon-512.png deleted file mode 100644 index 88cfd48dff1..00000000000 Binary files a/packages/dynamic_layouts/example/web/icons/Icon-512.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/web/icons/Icon-maskable-192.png b/packages/dynamic_layouts/example/web/icons/Icon-maskable-192.png deleted file mode 100644 index eb9b4d76e52..00000000000 Binary files a/packages/dynamic_layouts/example/web/icons/Icon-maskable-192.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/web/icons/Icon-maskable-512.png b/packages/dynamic_layouts/example/web/icons/Icon-maskable-512.png deleted file mode 100644 index d69c56691fb..00000000000 Binary files a/packages/dynamic_layouts/example/web/icons/Icon-maskable-512.png and /dev/null differ diff --git a/packages/dynamic_layouts/example/web/index.html b/packages/dynamic_layouts/example/web/index.html deleted file mode 100644 index 74802adda60..00000000000 --- a/packages/dynamic_layouts/example/web/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - example - - - - - - - - - - diff --git a/packages/dynamic_layouts/example/web/manifest.json b/packages/dynamic_layouts/example/web/manifest.json deleted file mode 100644 index 096edf8fe4c..00000000000 --- a/packages/dynamic_layouts/example/web/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "example", - "short_name": "example", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} diff --git a/packages/dynamic_layouts/example/windows/.gitignore b/packages/dynamic_layouts/example/windows/.gitignore deleted file mode 100644 index d492d0d98c8..00000000000 --- a/packages/dynamic_layouts/example/windows/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ diff --git a/packages/dynamic_layouts/example/windows/CMakeLists.txt b/packages/dynamic_layouts/example/windows/CMakeLists.txt deleted file mode 100644 index c0270746b1b..00000000000 --- a/packages/dynamic_layouts/example/windows/CMakeLists.txt +++ /dev/null @@ -1,101 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(example LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(SET CMP0063 NEW) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) diff --git a/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt b/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt deleted file mode 100644 index 903f4899d6f..00000000000 --- a/packages/dynamic_layouts/example/windows/flutter/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) diff --git a/packages/dynamic_layouts/example/windows/flutter/generated_plugins.cmake b/packages/dynamic_layouts/example/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c16..00000000000 --- a/packages/dynamic_layouts/example/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt b/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt deleted file mode 100644 index 17411a8ab8e..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/dynamic_layouts/example/windows/runner/Runner.rc b/packages/dynamic_layouts/example/windows/runner/Runner.rc deleted file mode 100644 index 0f5c0857111..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/Runner.rc +++ /dev/null @@ -1,121 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "example" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "example.exe" "\0" - VALUE "ProductName", "example" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/packages/dynamic_layouts/example/windows/runner/flutter_window.cpp b/packages/dynamic_layouts/example/windows/runner/flutter_window.cpp deleted file mode 100644 index 217bf9b69e6..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/flutter_window.cpp +++ /dev/null @@ -1,64 +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. -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} diff --git a/packages/dynamic_layouts/example/windows/runner/flutter_window.h b/packages/dynamic_layouts/example/windows/runner/flutter_window.h deleted file mode 100644 index 7cbf3d3ebbb..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/flutter_window.h +++ /dev/null @@ -1,36 +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. -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/dynamic_layouts/example/windows/runner/main.cpp b/packages/dynamic_layouts/example/windows/runner/main.cpp deleted file mode 100644 index 1285aabf714..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/main.cpp +++ /dev/null @@ -1,45 +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. -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t* command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"example", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} diff --git a/packages/dynamic_layouts/example/windows/runner/resource.h b/packages/dynamic_layouts/example/windows/runner/resource.h deleted file mode 100644 index d5d958dc425..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/packages/dynamic_layouts/example/windows/runner/resources/app_icon.ico b/packages/dynamic_layouts/example/windows/runner/resources/app_icon.ico deleted file mode 100644 index c04e20caf63..00000000000 Binary files a/packages/dynamic_layouts/example/windows/runner/resources/app_icon.ico and /dev/null differ diff --git a/packages/dynamic_layouts/example/windows/runner/runner.exe.manifest b/packages/dynamic_layouts/example/windows/runner/runner.exe.manifest deleted file mode 100644 index c977c4a4258..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/runner.exe.manifest +++ /dev/null @@ -1,20 +0,0 @@ - - - - - PerMonitorV2 - - - - - - - - - - - - - - - diff --git a/packages/dynamic_layouts/example/windows/runner/utils.cpp b/packages/dynamic_layouts/example/windows/runner/utils.cpp deleted file mode 100644 index 3bb0097f92d..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/utils.cpp +++ /dev/null @@ -1,66 +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. -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE* unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - int target_length = - ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, - nullptr, 0, nullptr, nullptr); - std::string utf8_string; - if (target_length == 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), - target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} diff --git a/packages/dynamic_layouts/example/windows/runner/utils.h b/packages/dynamic_layouts/example/windows/runner/utils.h deleted file mode 100644 index 6d1cc48f042..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/utils.h +++ /dev/null @@ -1,22 +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. -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ diff --git a/packages/dynamic_layouts/example/windows/runner/win32_window.cpp b/packages/dynamic_layouts/example/windows/runner/win32_window.cpp deleted file mode 100644 index 34738de2d35..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/win32_window.cpp +++ /dev/null @@ -1,240 +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. -#include "win32_window.h" - -#include - -#include "resource.h" - -namespace { - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); - } -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { ++g_active_window_count; } - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - return OnCreate(); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { return window_handle_; } - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} diff --git a/packages/dynamic_layouts/example/windows/runner/win32_window.h b/packages/dynamic_layouts/example/windows/runner/win32_window.h deleted file mode 100644 index 0f8bd1b7f92..00000000000 --- a/packages/dynamic_layouts/example/windows/runner/win32_window.h +++ /dev/null @@ -1,98 +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. -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates and shows a win32 window with |title| and position and size using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, const Point& origin, - const Size& size); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/dynamic_layouts/lib/dynamic_layouts.dart b/packages/dynamic_layouts/lib/dynamic_layouts.dart deleted file mode 100644 index de26b07b37b..00000000000 --- a/packages/dynamic_layouts/lib/dynamic_layouts.dart +++ /dev/null @@ -1,9 +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. - -export 'src/base_grid_layout.dart'; -export 'src/dynamic_grid.dart'; -export 'src/render_dynamic_grid.dart'; -export 'src/staggered_layout.dart'; -export 'src/wrap_layout.dart'; diff --git a/packages/dynamic_layouts/lib/src/base_grid_layout.dart b/packages/dynamic_layouts/lib/src/base_grid_layout.dart deleted file mode 100644 index df78c45b082..00000000000 --- a/packages/dynamic_layouts/lib/src/base_grid_layout.dart +++ /dev/null @@ -1,113 +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 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; - -/// Describes the placement of a child in a [RenderDynamicSliverGrid]. -class DynamicSliverGridGeometry extends SliverGridGeometry { - /// Creates an object that describes the placement of a child in a - /// [RenderDynamicSliverGrid]. - const DynamicSliverGridGeometry({ - required super.scrollOffset, - required super.crossAxisOffset, - required super.mainAxisExtent, - required super.crossAxisExtent, - }); - - /// Returns [BoxConstraints] that will be tight if the - /// [DynamicSliverGridLayout] has provided fixed extents, forcing the child to - /// have the required size. - /// - /// If the [mainAxisExtent] is [double.infinity] the child will be allowed to - /// choose its own size in the main axis. Similarly, an infinite - /// [crossAxisExtent] will result in the child sizing itself in the cross - /// axis. Otherwise, the provided cross axis size or the - /// [SliverConstraints.crossAxisExtent] will be used to create tight - /// constraints in the cross axis. - /// - /// This differs from [SliverGridGeometry.getBoxConstraints] in that it allows - /// loose constraints, allowing the child to be its preferred size, or within - /// a range of minimum and maximum extents. - @override - BoxConstraints getBoxConstraints(SliverConstraints constraints) { - final double mainMinExtent = mainAxisExtent.isFinite ? mainAxisExtent : 0; - final double crossMinExtent = - crossAxisExtent.isInfinite ? 0.0 : crossAxisExtent; - - switch (constraints.axis) { - case Axis.vertical: - return BoxConstraints( - minHeight: mainMinExtent, - maxHeight: mainAxisExtent, - minWidth: crossMinExtent, - maxWidth: crossAxisExtent, - ); - case Axis.horizontal: - return BoxConstraints( - minHeight: crossMinExtent, - maxHeight: crossAxisExtent, - minWidth: mainMinExtent, - maxWidth: mainAxisExtent, - ); - } - } -} - -/// Manages the size and position of all the tiles in a [RenderSliverGrid]. -/// -/// Rather than providing a grid with a [SliverGridLayout] directly, you instead -/// provide the grid with a [SliverGridDelegate], which can compute a -/// [SliverGridLayout] given the current [SliverConstraints]. -/// -/// {@template dynamicLayouts.garbageCollection} -/// This grid does not currently collect leading garbage as the user scrolls -/// further down. This is because the current implementation requires the -/// leading tiles to maintain the current layout. Follow -/// [this github issue](https://github.com/flutter/flutter/issues/112234) for -/// tracking progress on dynamic leading garbage collection. -/// {@endtemplate} -abstract class DynamicSliverGridLayout extends SliverGridLayout { - /// The estimated size and position of the child with the given index. - /// - /// The [DynamicSliverGridGeometry] that is returned will - /// provide looser constraints to the child, whose size after layout can be - /// reported back to the layout object in [updateGeometryForChildIndex]. - @override - DynamicSliverGridGeometry getGeometryForChildIndex(int index); - - /// Update the size and position of the child with the given index, - /// considering the size of the child after layout. - /// - /// This is used to update the layout object after the child has laid out, - /// allowing the layout pattern to adapt to the child's size. - DynamicSliverGridGeometry updateGeometryForChildIndex( - int index, - Size childSize, - ); - - /// Called by [RenderDynamicSliverGrid] to validate the layout pattern has - /// filled the screen. - /// - /// A given child may have reached the target scroll offset of the current - /// layout pass, but there may still be more children to lay out based on the - /// pattern. - bool reachedTargetScrollOffset(double targetOffset); - - // These methods are not relevant to dynamic grid building, but extending the - // base [SliverGridLayout] class allows us to re-use existing - // [SliverGridDelegate]s like [SliverGridDelegateWithFixedCrossAxisCount] and - // [SliverGridDelegateWithMaxCrossAxisExtent]. - @override - @mustCallSuper - double computeMaxScrollOffset(int childCount) => throw UnimplementedError(); - @override - @mustCallSuper - int getMaxChildIndexForScrollOffset(double scrollOffset) => - throw UnimplementedError(); - @override - @mustCallSuper - int getMinChildIndexForScrollOffset(double scrollOffset) => - throw UnimplementedError(); -} diff --git a/packages/dynamic_layouts/lib/src/dynamic_grid.dart b/packages/dynamic_layouts/lib/src/dynamic_grid.dart deleted file mode 100644 index 8eedfe40c31..00000000000 --- a/packages/dynamic_layouts/lib/src/dynamic_grid.dart +++ /dev/null @@ -1,256 +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 'package:flutter/widgets.dart'; - -import 'render_dynamic_grid.dart'; -import 'staggered_layout.dart'; -import 'wrap_layout.dart'; - -/// A scrollable grid of widgets, capable of dynamically positioning tiles based -/// on different aspects of the children's size when laid out with loose -/// constraints. -/// -/// Three [SliverGridDelegate]s support wrapping or staggering the dynamically -/// sized children of the grid. Staggering children can use -/// [SliverGridDelegateWithFixedCrossAxisCount] or -/// [SliverGridDelegateWithMaxCrossAxisExtent], while wrapping uses its own -/// [SliverGridDelegateWithWrapping]. -/// -/// {@macro dynamicLayouts.garbageCollection} -/// -/// The following example shows how to use the [DynamicGridView.wrap] -/// constructor. -/// -/// ```dart -/// DynamicGridView.wrap( -/// mainAxisSpacing: 10, -/// crossAxisSpacing: 20, -/// children: [ -/// Container( -/// height: 100, -/// width: 200, -/// color: Colors.amberAccent[100], -/// child: const Center(child: Text('Item 1') -/// ), -/// ), -/// Container( -/// height: 50, -/// width: 70, -/// color: Colors.blue[100], -/// child: const Center(child: Text('Item 2'), -/// ), -/// ), -/// Container( -/// height: 82, -/// width: 300, -/// color: Colors.pink[100], -/// child: const Center(child: Text('Item 3'), -/// ), -/// ), -/// Container( -/// color: Colors.green[100], -/// child: const Center(child: Text('Item 3'), -/// ), -/// ), -/// ], -/// ), -/// ``` -/// -/// This sample code shows how to use the [DynamicGridView.staggered] -/// constructor with a [maxCrossAxisExtent]: -/// -/// ```dart -/// DynamicGridView.staggered( -/// maxCrossAxisExtent: 100, -/// crossAxisSpacing: 2, -/// mainAxisSpacing: 2, -/// children: List.generate( -/// 50, -/// (int index) => Container( -/// height: index % 3 * 50 + 20, -/// color: Colors.amber[index % 9 * 100], -/// child: Center(child: Text("Index $index")), -/// ), -/// ), -/// ); -/// ``` -class DynamicGridView extends GridView { - /// Creates a scrollable, 2D array of widgets with a custom - /// [SliverGridDelegate]. - DynamicGridView({ - super.key, - super.scrollDirection, - super.reverse, - required super.gridDelegate, - // This creates a SliverChildListDelegate in the super class. - super.children = const [], - }); - - /// Creates a scrollable, 2D array of widgets that are created on demand. - DynamicGridView.builder({ - super.key, - super.scrollDirection, - super.reverse, - required super.gridDelegate, - // This creates a SliverChildBuilderDelegate in the super class. - required super.itemBuilder, - super.itemCount, - }) : super.builder(); - - /// Creates a scrollable, 2D array of widgets with tiles where each tile can - /// have its own size. - /// - /// Uses a [SliverGridDelegateWithWrapping] as the [gridDelegate]. - /// - /// The following example shows how to use the DynamicGridView.wrap constructor. - /// - /// ```dart - /// DynamicGridView.wrap( - /// mainAxisSpacing: 10, - /// crossAxisSpacing: 20, - /// children: [ - /// Container( - /// height: 100, - /// width: 200, - /// color: Colors.amberAccent[100], - /// child: const Center(child: Text('Item 1') - /// ), - /// ), - /// Container( - /// height: 50, - /// width: 70, - /// color: Colors.blue[100], - /// child: const Center(child: Text('Item 2'), - /// ), - /// ), - /// Container( - /// height: 82, - /// width: 300, - /// color: Colors.pink[100], - /// child: const Center(child: Text('Item 3'), - /// ), - /// ), - /// Container( - /// color: Colors.green[100], - /// child: const Center(child: Text('Item 3'), - /// ), - /// ), - /// ], - /// ), - /// ``` - /// - /// See also: - /// - /// * [SliverGridDelegateWithWrapping] to see a more detailed explanation of - /// how the wrapping works. - DynamicGridView.wrap({ - super.key, - super.scrollDirection, - super.reverse, - double mainAxisSpacing = 0.0, - double crossAxisSpacing = 0.0, - double childCrossAxisExtent = double.infinity, - double childMainAxisExtent = double.infinity, - super.children = const [], - }) : super( - gridDelegate: SliverGridDelegateWithWrapping( - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - childCrossAxisExtent: childCrossAxisExtent, - childMainAxisExtent: childMainAxisExtent, - ), - ); - - /// Creates a scrollable, 2D array of widgets where each tile's main axis - /// extent will be determined by the child's corresponding finite size and - /// the cross axis extent will be fixed, generating a staggered layout. - /// - /// Either a [crossAxisCount] or a [maxCrossAxisExtent] must be provided. - /// The constructor will then use a - /// [DynamicSliverGridDelegateWithFixedCrossAxisCount] or a - /// [DynamicSliverGridDelegateWithMaxCrossAxisExtent] as its [gridDelegate], - /// respectively. - /// - /// This sample code shows how to use the constructor with a - /// [maxCrossAxisExtent] and a simple layout: - /// - /// ```dart - /// DynamicGridView.staggered( - /// maxCrossAxisExtent: 100, - /// crossAxisSpacing: 2, - /// mainAxisSpacing: 2, - /// children: List.generate( - /// 50, - /// (int index) => Container( - /// height: index % 3 * 50 + 20, - /// color: Colors.amber[index % 9 * 100], - /// child: Center(child: Text("Index $index")), - /// ), - /// ), - /// ); - /// ``` - DynamicGridView.staggered({ - super.key, - super.scrollDirection, - super.reverse, - int? crossAxisCount, - double? maxCrossAxisExtent, - double mainAxisSpacing = 0.0, - double crossAxisSpacing = 0.0, - super.children = const [], - }) : assert(crossAxisCount != null || maxCrossAxisExtent != null), - assert(crossAxisCount == null || maxCrossAxisExtent == null), - super( - gridDelegate: crossAxisCount != null - ? DynamicSliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - ) - : DynamicSliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: maxCrossAxisExtent!, - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - ), - ); - - @override - Widget buildChildLayout(BuildContext context) { - return DynamicSliverGrid( - delegate: childrenDelegate, - gridDelegate: gridDelegate, - ); - } -} - -/// A sliver that places multiple box children in a two dimensional arrangement. -class DynamicSliverGrid extends SliverMultiBoxAdaptorWidget { - /// Creates a sliver that places multiple box children in a two dimensional - /// arrangement. - const DynamicSliverGrid({ - super.key, - required super.delegate, - required this.gridDelegate, - }); - - /// The delegate that manages the size and position of the children. - final SliverGridDelegate gridDelegate; - - @override - RenderDynamicSliverGrid createRenderObject(BuildContext context) { - final SliverMultiBoxAdaptorElement element = - context as SliverMultiBoxAdaptorElement; - return RenderDynamicSliverGrid( - childManager: element, gridDelegate: gridDelegate); - } - - @override - void updateRenderObject( - BuildContext context, - RenderDynamicSliverGrid renderObject, - ) { - renderObject.gridDelegate = gridDelegate; - } -} diff --git a/packages/dynamic_layouts/lib/src/render_dynamic_grid.dart b/packages/dynamic_layouts/lib/src/render_dynamic_grid.dart deleted file mode 100644 index 468348dc1cf..00000000000 --- a/packages/dynamic_layouts/lib/src/render_dynamic_grid.dart +++ /dev/null @@ -1,402 +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 'dart:math' as math; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; - -import 'base_grid_layout.dart'; - -/// A sliver that places multiple box children in a two dimensional arrangement. -/// -/// [RenderDynamicSliverGrid] places its children in arbitrary positions determined by -/// the [DynamicSliverGridLayout] provided by the [gridDelegate]. -/// -/// {@macro dynamicLayouts.garbageCollection} -class RenderDynamicSliverGrid extends RenderSliverGrid { - /// Creates a sliver that contains multiple box children whose size and - /// position are managed by a delegate. - /// - /// The [childManager] and [gridDelegate] arguments must not be null. - RenderDynamicSliverGrid({ - required super.childManager, - required super.gridDelegate, - }); - - @override - void performLayout() { - final SliverConstraints constraints = this.constraints; - childManager.didStartLayout(); - childManager.setDidUnderflow(false); - - final double scrollOffset = - constraints.scrollOffset + constraints.cacheOrigin; - assert(scrollOffset >= 0.0); - final double remainingExtent = constraints.remainingCacheExtent; - assert(remainingExtent >= 0.0); - final double targetEndScrollOffset = scrollOffset + remainingExtent; - final DynamicSliverGridLayout layout = - gridDelegate.getLayout(constraints) as DynamicSliverGridLayout; - int leadingGarbage = 0; - int trailingGarbage = 0; - bool reachedEnd = false; - - // This algorithm in principle is straight-forward: find the first child - // that overlaps the given scrollOffset, creating more children at the top - // of the grid if necessary, then walk through the grid updating and laying - // out each child and adding more at the end if necessary until we have - // enough children to cover the entire viewport. - // - // It is complicated by one minor issue, which is that any time you update - // or create a child, it's possible that some of the children that - // haven't yet been laid out will be removed, leaving the list in an - // inconsistent state, and requiring that missing nodes be recreated. - // - // To keep this mess tractable, this algorithm starts from what is currently - // the first child, if any, and then walks up and/or down from there, so - // that the nodes that might get removed are always at the edges of what has - // already been laid out. - - // Make sure we have at least one child to start from. - if (firstChild == null && !addInitialChild()) { - // There are no children. - geometry = SliverGeometry.zero; - childManager.didFinishLayout(); - return; - } - - // We have at least one child. - assert(firstChild != null); - - // These variables track the range of children that we have laid out. Within - // this range, the children have consecutive indices. Outside this range, - // it's possible for a child to get removed without notice. - RenderBox? leadingChildWithLayout, trailingChildWithLayout; - RenderBox? earliestUsefulChild = firstChild; - - // A firstChild with null layout offset is likely a result of children - // reordering. - // - // We rely on firstChild to have accurate layout offset. In the case of null - // layout offset, we have to find the first child that has valid layout - // offset. - if (childScrollOffset(firstChild!) == null) { - int leadingChildrenWithoutLayoutOffset = 0; - while (earliestUsefulChild != null && - childScrollOffset(earliestUsefulChild) == null) { - earliestUsefulChild = childAfter(earliestUsefulChild); - leadingChildrenWithoutLayoutOffset += 1; - } - // We should be able to destroy children with null layout offset safely, - // because they are likely outside of viewport - collectGarbage(leadingChildrenWithoutLayoutOffset, 0); - // If we cannot find a valid layout offset, start from the initial child. - if (firstChild == null && !addInitialChild()) { - // There are no children. - geometry = SliverGeometry.zero; - childManager.didFinishLayout(); - return; - } - } - - // Find the last child that is at or before the scrollOffset. - earliestUsefulChild = firstChild; - assert(earliestUsefulChild != null); - for (int index = indexOf(earliestUsefulChild!) - 1; - childScrollOffset(earliestUsefulChild!)! > scrollOffset; - --index) { - final double earliestScrollOffset = - childScrollOffset(earliestUsefulChild)!; - // We have to add children before the earliestUsefulChild. - SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); - earliestUsefulChild = insertAndLayoutLeadingChild( - gridGeometry.getBoxConstraints(constraints), - parentUsesSize: true, - ); - // There are no more preceding children. - if (earliestUsefulChild == null) { - final SliverGridParentData childParentData = - firstChild!.parentData! as SliverGridParentData; - childParentData.layoutOffset = gridGeometry.scrollOffset; - childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; - - if (scrollOffset == 0.0) { - // insertAndLayoutLeadingChild only lays out the children before - // firstChild. In this case, nothing has been laid out. We have - // to lay out firstChild manually. - gridGeometry = layout.getGeometryForChildIndex(0); - firstChild!.layout(gridGeometry.getBoxConstraints(constraints), - parentUsesSize: true); - earliestUsefulChild = firstChild; - leadingChildWithLayout = earliestUsefulChild; - trailingChildWithLayout ??= earliestUsefulChild; - break; - } else { - // We ran out of children before reaching the scroll offset. - // We must inform our parent that this sliver cannot fulfill - // its contract and that we need a scroll offset correction. - geometry = SliverGeometry( - scrollOffsetCorrection: -scrollOffset, - ); - return; - } - } - - final double firstChildScrollOffset = - earliestScrollOffset - paintExtentOf(firstChild!); - // firstChildScrollOffset may contain double precision error - if (firstChildScrollOffset < -precisionErrorTolerance) { - // Let's assume there is no child before the first child. We will - // correct it on the next layout if it is not. - geometry = SliverGeometry( - scrollOffsetCorrection: -firstChildScrollOffset, - ); - final SliverGridParentData childParentData = - firstChild!.parentData! as SliverGridParentData; - childParentData.layoutOffset = gridGeometry.scrollOffset; - childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; - return; - } - - final SliverGridParentData childParentData = - earliestUsefulChild.parentData! as SliverGridParentData; - gridGeometry = layout.updateGeometryForChildIndex( - indexOf(earliestUsefulChild), earliestUsefulChild.size); - childParentData.layoutOffset = gridGeometry.scrollOffset; - childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; - assert(earliestUsefulChild == firstChild); - leadingChildWithLayout = earliestUsefulChild; - trailingChildWithLayout ??= earliestUsefulChild; - } - - assert(childScrollOffset(firstChild!)! > -precisionErrorTolerance); - - // If the scroll offset is at zero, we should make sure we are - // actually at the beginning of the list. - if (scrollOffset < precisionErrorTolerance) { - // We iterate from the firstChild in case the leading child has a 0 paint - // extent. - int indexOfFirstChild = indexOf(firstChild!); - while (indexOfFirstChild > 0) { - final double earliestScrollOffset = childScrollOffset(firstChild!)!; - // We correct one child at a time. If there are more children before - // the earliestUsefulChild, we will correct it once the scroll offset - // reaches zero again. - SliverGridGeometry gridGeometry = - layout.getGeometryForChildIndex(indexOfFirstChild - 1); - earliestUsefulChild = insertAndLayoutLeadingChild( - gridGeometry.getBoxConstraints(constraints), - parentUsesSize: true, - ); - assert(earliestUsefulChild != null); - final double firstChildScrollOffset = - earliestScrollOffset - paintExtentOf(firstChild!); - final SliverGridParentData childParentData = - firstChild!.parentData! as SliverGridParentData; - gridGeometry = layout.updateGeometryForChildIndex( - indexOfFirstChild - 1, firstChild!.size); - childParentData.layoutOffset = gridGeometry.scrollOffset; - childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; - // We only need to correct if the leading child actually has a - // paint extent. - if (firstChildScrollOffset < -precisionErrorTolerance) { - geometry = SliverGeometry( - scrollOffsetCorrection: -firstChildScrollOffset, - ); - return; - } - indexOfFirstChild = indexOf(firstChild!); - } - } - - // At this point, earliestUsefulChild is the first child, and is a child - // whose scrollOffset is at or before the scrollOffset, and - // leadingChildWithLayout and trailingChildWithLayout are either null or - // cover a range of render boxes that we have laid out with the first being - // the same as earliestUsefulChild and the last being either at or after the - // scroll offset. - - assert(earliestUsefulChild == firstChild); - assert(childScrollOffset(earliestUsefulChild!)! <= scrollOffset); - - // Make sure we've laid out at least one child. - if (leadingChildWithLayout == null) { - final int index = indexOf(earliestUsefulChild!); - SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); - earliestUsefulChild.layout( - gridGeometry.getBoxConstraints(constraints), - parentUsesSize: true, - ); - leadingChildWithLayout = earliestUsefulChild; - trailingChildWithLayout = earliestUsefulChild; - gridGeometry = - layout.updateGeometryForChildIndex(index, earliestUsefulChild.size); - final SliverGridParentData childParentData = - earliestUsefulChild.parentData! as SliverGridParentData; - childParentData.layoutOffset = gridGeometry.scrollOffset; - childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; - } - - // Here, earliestUsefulChild is still the first child, it's got a - // scrollOffset that is at or before our actual scrollOffset, and it has - // been laid out, and is in fact our leadingChildWithLayout. It's possible - // that some children beyond that one have also been laid out. - - bool inLayoutRange = true; - RenderBox? child = earliestUsefulChild; - int index = indexOf(child!); - double endScrollOffset = childScrollOffset(child)! + paintExtentOf(child); - bool advance() { - // returns true if we advanced, false if we have no more children - // This function is used in two different places below, to avoid code duplication. - late SliverGridGeometry gridGeometry; - assert(child != null); - if (child == trailingChildWithLayout) { - inLayoutRange = false; - } - child = childAfter(child!); - if (child == null) { - inLayoutRange = false; - } - index += 1; - if (!inLayoutRange) { - if (child == null || indexOf(child!) != index) { - // We are missing a child. Insert it (and lay it out) if possible. - gridGeometry = layout - .getGeometryForChildIndex(indexOf(trailingChildWithLayout!) + 1); - child = insertAndLayoutChild( - gridGeometry.getBoxConstraints(constraints), - after: trailingChildWithLayout, - parentUsesSize: true, - ); - if (child == null) { - // We have run out of children. - return false; - } - } else { - // Lay out the child. - assert(indexOf(child!) == index); - gridGeometry = layout.getGeometryForChildIndex(index); - child!.layout( - gridGeometry.getBoxConstraints(constraints), - parentUsesSize: true, - ); - } - trailingChildWithLayout = child; - } - assert(child != null); - final SliverGridParentData childParentData = - child!.parentData! as SliverGridParentData; - gridGeometry = layout.updateGeometryForChildIndex(index, child!.size); - childParentData.layoutOffset = gridGeometry.scrollOffset; - childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; - assert(childParentData.index == index); - // The current child may not extend past a previously laid out child. - endScrollOffset = math.max( - endScrollOffset, - childScrollOffset(child!)! + paintExtentOf(child!), - ); - return true; - } - - // Find the first child that ends after the scroll offset. - while (endScrollOffset < scrollOffset) { - leadingGarbage += 1; - if (!advance()) { - assert(leadingGarbage == childCount); - assert(child == null); - // we want to make sure we keep the last child around so we know the end - // scroll offset - collectGarbage(leadingGarbage - 1, 0); - assert(firstChild == lastChild); - final double extent = - childScrollOffset(lastChild!)! + paintExtentOf(lastChild!); - geometry = SliverGeometry( - scrollExtent: extent, - maxPaintExtent: extent, - ); - return; - } - } - - // Now find the first child that ends after our end. - while (endScrollOffset < targetEndScrollOffset || - !layout.reachedTargetScrollOffset(targetEndScrollOffset)) { - if (!advance()) { - reachedEnd = true; - break; - } - } - - // Finally count up all the remaining children and label them as garbage. - if (child != null) { - child = childAfter(child!); - while (child != null) { - trailingGarbage += 1; - child = childAfter(child!); - } - } - - // At this point everything should be good to go, we just have to clean up - // the garbage and report the geometry. - // We only collect trailing garbage because - // - // 1 - dynamic tile placement is dependent on the preceding tile, - // potentially, the SliverGridLayout could cache the placement of each - // tile to refer back to, but that would mean tiles could not change in - // size or be added/removed without having to recompute the whole layout. - // - // 2 - Currently, Flutter only supports collecting garbage in sequential - // order. Dynamic layout patterns can break this assumption. In order to - // truly collect garbage efficiently, support for non-sequential garbage - // collection is necessary. - // TODO(all): https://github.com/flutter/flutter/issues/112234 - collectGarbage(0, trailingGarbage); - - assert(debugAssertChildListIsNonEmptyAndContiguous()); - final double estimatedMaxScrollOffset; - if (reachedEnd) { - estimatedMaxScrollOffset = endScrollOffset; - } else { - estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset( - constraints, - firstIndex: indexOf(firstChild!), - lastIndex: indexOf(lastChild!), - leadingScrollOffset: childScrollOffset(firstChild!), - trailingScrollOffset: endScrollOffset, - ); - assert(estimatedMaxScrollOffset >= - endScrollOffset - childScrollOffset(firstChild!)!); - } - final double paintExtent = calculatePaintOffset( - constraints, - from: childScrollOffset(firstChild!)!, - to: endScrollOffset, - ); - final double cacheExtent = calculateCacheOffset( - constraints, - from: childScrollOffset(firstChild!)!, - to: endScrollOffset, - ); - final double targetEndScrollOffsetForPaint = - constraints.scrollOffset + constraints.remainingPaintExtent; - geometry = SliverGeometry( - scrollExtent: estimatedMaxScrollOffset, - paintExtent: paintExtent, - cacheExtent: cacheExtent, - maxPaintExtent: estimatedMaxScrollOffset, - // Conservative to avoid flickering away the clip during scroll. - hasVisualOverflow: endScrollOffset > targetEndScrollOffsetForPaint || - constraints.scrollOffset > 0.0, - ); - - // We may have started the layout while scrolled to the end, which would not - // expose a new child. - if (estimatedMaxScrollOffset == endScrollOffset) { - childManager.setDidUnderflow(true); - } - childManager.didFinishLayout(); - } -} diff --git a/packages/dynamic_layouts/lib/src/staggered_layout.dart b/packages/dynamic_layouts/lib/src/staggered_layout.dart deleted file mode 100644 index f5fb4fb86a0..00000000000 --- a/packages/dynamic_layouts/lib/src/staggered_layout.dart +++ /dev/null @@ -1,318 +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 'dart:math' as math; - -import 'package:flutter/rendering.dart'; - -import 'base_grid_layout.dart'; -import 'render_dynamic_grid.dart'; -import 'wrap_layout.dart'; - -/// A [DynamicSliverGridLayout] that creates tiles with varying main axis -/// sizes and fixed cross axis sizes, generating a staggered layout. The extent -/// in the main axis will be defined by the child's size and must be finite. -/// Similar to Android's StaggeredGridLayoutManager: -/// [https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager]. -/// -/// The tiles are placed in the column (or row in case [scrollDirection] is set -/// to [Axis.horizontal]) with the minimum extent, which means children are not -/// necessarily laid out in sequential order. -/// -/// See also: -/// -/// * [SliverGridWrappingTileLayout], a similar layout that allows tiles to be -/// freely sized in the main and cross axis. -/// * [DynamicSliverGridDelegateWithMaxCrossAxisExtent], which creates -/// staggered layouts with a maximum extent in the cross axis. -/// * [DynamicSliverGridDelegateWithFixedCrossAxisCount], which creates -/// staggered layouts with a consistent amount of tiles in the cross axis. -/// * [DynamicGridView.staggered], which uses these delegates to create -/// staggered layouts. -/// * [DynamicSliverGridGeometry], which establishes the position of a child -/// and pass the child's desired proportions to a [DynamicSliverGridLayout]. -/// * [RenderDynamicSliverGrid], which is the sliver where the dynamic sized -/// tiles are positioned. -class SliverGridStaggeredTileLayout extends DynamicSliverGridLayout { - /// Creates a layout with dynamic main axis extents determined by the child's - /// size and fixed cross axis extents. - /// - /// All arguments must be not null. The [crossAxisCount] argument must be - /// greater than zero. The [mainAxisSpacing], [crossAxisSpacing] and - /// [childCrossAxisExtent] arguments must not be negative. - SliverGridStaggeredTileLayout({ - required this.crossAxisCount, - required this.mainAxisSpacing, - required this.crossAxisSpacing, - required this.childCrossAxisExtent, - required this.scrollDirection, - }) : assert(crossAxisCount > 0), - assert(crossAxisSpacing >= 0), - assert(childCrossAxisExtent >= 0); - - /// The number of children in the cross axis. - final int crossAxisCount; - - /// The number of logical pixels between each child along the main axis. - final double mainAxisSpacing; - - /// The number of logical pixels between each child along the cross axis. - final double crossAxisSpacing; - - /// The number of pixels from the leading edge of one tile to the trailing - /// edge of the same tile in the cross axis. - final double childCrossAxisExtent; - - /// The axis along which the scroll view scrolls. - final Axis scrollDirection; - - /// The collection of scroll offsets for every row or column across the main - /// axis. It includes the tiles' sizes and the spacing between them. - final List _scrollOffsetForMainAxis = []; - - /// The amount of tiles in every row or column across the main axis. - final List _mainAxisCount = []; - - /// Returns the row or column with the minimum extent for the next child to - /// be laid out. - int _getNextCrossAxisSlot() { - int nextCrossAxisSlot = 0; - double minScrollOffset = double.infinity; - - if (_scrollOffsetForMainAxis.length < crossAxisCount) { - nextCrossAxisSlot = _scrollOffsetForMainAxis.length; - _scrollOffsetForMainAxis.add(0.0); - return nextCrossAxisSlot; - } - - for (int i = 0; i < crossAxisCount; i++) { - if (_scrollOffsetForMainAxis[i] < minScrollOffset) { - nextCrossAxisSlot = i; - minScrollOffset = _scrollOffsetForMainAxis[i]; - } - } - return nextCrossAxisSlot; - } - - @override - bool reachedTargetScrollOffset(double targetOffset) { - for (final double scrollOffset in _scrollOffsetForMainAxis) { - if (scrollOffset < targetOffset) { - return false; - } - } - return true; - } - - @override - DynamicSliverGridGeometry getGeometryForChildIndex(int index) { - return DynamicSliverGridGeometry( - scrollOffset: 0.0, - crossAxisOffset: 0.0, - mainAxisExtent: double.infinity, - crossAxisExtent: childCrossAxisExtent, - ); - } - - @override - DynamicSliverGridGeometry updateGeometryForChildIndex( - int index, - Size childSize, - ) { - final int crossAxisSlot = _getNextCrossAxisSlot(); - final double currentScrollOffset = _scrollOffsetForMainAxis[crossAxisSlot]; - final double childMainAxisExtent = - scrollDirection == Axis.vertical ? childSize.height : childSize.width; - final double scrollOffset = currentScrollOffset + - (_mainAxisCount.length >= crossAxisCount - ? _mainAxisCount[crossAxisSlot] - : 0) * - mainAxisSpacing; - final double crossAxisOffset = - crossAxisSlot * (childCrossAxisExtent + crossAxisSpacing); - final double mainAxisExtent = - scrollDirection == Axis.vertical ? childSize.height : childSize.width; - _scrollOffsetForMainAxis[crossAxisSlot] = - childMainAxisExtent + _scrollOffsetForMainAxis[crossAxisSlot]; - _mainAxisCount.length >= crossAxisCount - ? _mainAxisCount[crossAxisSlot] += 1 - : _mainAxisCount.add(1); - - return DynamicSliverGridGeometry( - scrollOffset: scrollOffset, - crossAxisOffset: crossAxisOffset, - mainAxisExtent: mainAxisExtent, - crossAxisExtent: childCrossAxisExtent, - ); - } -} - -/// Creates dynamic grid layouts with a fixed number of tiles in the cross axis -/// and varying main axis size dependent on the child's corresponding finite -/// extent. It uses the same logic as -/// [SliverGridDelegateWithFixedCrossAxisCount] where the total extent in the -/// cross axis is distributed equally between the specified amount of tiles, -/// but with a [SliverGridStaggeredTileLayout]. -/// -/// For example, if the grid is vertical, this delegate will create a layout -/// with a fixed number of columns. If the grid is horizontal, this delegate -/// will create a layout with a fixed number of rows. -/// -/// This sample code shows how to use it independently with a [DynamicGridView] -/// constructor: -/// -/// ```dart -/// DynamicGridView( -/// gridDelegate: const DynamicSliverGridDelegateWithFixedCrossAxisCount( -/// crossAxisCount: 4, -/// ), -/// children: List.generate( -/// 50, -/// (int index) => SizedBox( -/// height: index % 2 * 20 + 20, -/// child: Text('Index $index'), -/// ), -/// ), -/// ); -/// ``` -/// -/// See also: -/// -/// * [DynamicSliverGridDelegateWithMaxCrossAxisExtent], which creates a -/// dynamic layout with tiles that have a maximum cross-axis extent -/// and varying main axis size. -/// * [DynamicGridView], which can use this delegate to control the layout of -/// its tiles. -/// * [DynamicSliverGridGeometry], which establishes the position of a child -/// and pass the child's desired proportions to [DynamicSliverGridLayout]. -/// * [RenderDynamicSliverGrid], which is the sliver where the dynamic sized -/// tiles are positioned. -class DynamicSliverGridDelegateWithFixedCrossAxisCount - extends SliverGridDelegateWithFixedCrossAxisCount { - /// Creates a delegate that makes grid layouts with a fixed number of tiles - /// in the cross axis and varying main axis size dependent on the child's - /// corresponding finite extent. - /// - /// Only the [crossAxisCount] argument needs to be greater than zero. All of - /// them must be not null. - const DynamicSliverGridDelegateWithFixedCrossAxisCount({ - required super.crossAxisCount, - super.mainAxisSpacing = 0.0, - super.crossAxisSpacing = 0.0, - }) : assert(crossAxisCount > 0), - assert(mainAxisSpacing >= 0), - assert(crossAxisSpacing >= 0); - - bool _debugAssertIsValid() { - assert(crossAxisCount > 0); - assert(mainAxisSpacing >= 0.0); - assert(crossAxisSpacing >= 0.0); - assert(childAspectRatio > 0.0); - return true; - } - - @override - DynamicSliverGridLayout getLayout(SliverConstraints constraints) { - assert(_debugAssertIsValid()); - final double usableCrossAxisExtent = math.max( - 0.0, - constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1), - ); - final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; - return SliverGridStaggeredTileLayout( - crossAxisCount: crossAxisCount, - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - childCrossAxisExtent: childCrossAxisExtent, - scrollDirection: axisDirectionToAxis(constraints.axisDirection), - ); - } -} - -/// Creates dynamic grid layouts with tiles that each have a maximum cross-axis -/// extent and varying main axis size dependent on the child's corresponding -/// finite extent. It uses the same logic as -/// [SliverGridDelegateWithMaxCrossAxisExtent] where every tile has the same -/// cross axis size and does not exceed the provided max extent, but with a -/// [SliverGridStaggeredTileLayout]. -/// -/// This delegate will select a cross-axis extent for the tiles that is as -/// large as possible subject to the following conditions: -/// -/// * The extent evenly divides the cross-axis extent of the grid. -/// * The extent is at most [maxCrossAxisExtent]. -/// -/// This sample code shows how to use it independently with a [DynamicGridView] -/// constructor: -/// -/// ```dart -/// DynamicGridView( -/// gridDelegate: const DynamicSliverGridDelegateWithMaxCrossAxisExtent( -/// maxCrossAxisExtent: 100, -/// ), -/// children: List.generate( -/// 50, -/// (int index) => SizedBox( -/// height: index % 2 * 20 + 20, -/// child: Text('Index $index'), -/// ), -/// ), -/// ); -/// ``` -/// -/// See also: -/// -/// * [DynamicSliverGridDelegateWithFixedCrossAxisCount], which creates a -/// layout with a fixed number of tiles in the cross axis. -/// * [DynamicGridView], which can use this delegate to control the layout of -/// its tiles. -/// * [DynamicSliverGridGeometry], which establishes the position of a child -/// and pass the child's desired proportions to [DynamicSliverGridLayout]. -/// * [RenderDynamicSliverGrid], which is the sliver where the dynamic sized -/// tiles are positioned. -class DynamicSliverGridDelegateWithMaxCrossAxisExtent - extends SliverGridDelegateWithMaxCrossAxisExtent { - /// Creates a delegate that makes grid layouts with tiles that have a maximum - /// cross-axis extent and varying main axis size dependent on the child's - /// corresponding finite extent. - /// - /// Only the [maxCrossAxisExtent] argument needs to be greater than zero. - /// All of them must be not null. - const DynamicSliverGridDelegateWithMaxCrossAxisExtent({ - required super.maxCrossAxisExtent, - super.mainAxisSpacing = 0.0, - super.crossAxisSpacing = 0.0, - }) : assert(maxCrossAxisExtent > 0), - assert(mainAxisSpacing >= 0), - assert(crossAxisSpacing >= 0); - - bool _debugAssertIsValid(double crossAxisExtent) { - assert(crossAxisExtent > 0.0); - assert(maxCrossAxisExtent > 0.0); - assert(mainAxisSpacing >= 0.0); - assert(crossAxisSpacing >= 0.0); - assert(childAspectRatio > 0.0); - return true; - } - - @override - DynamicSliverGridLayout getLayout(SliverConstraints constraints) { - assert(_debugAssertIsValid(constraints.crossAxisExtent)); - final int crossAxisCount = - (constraints.crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)) - .ceil(); - final double usableCrossAxisExtent = math.max( - 0.0, - constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1), - ); - final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; - return SliverGridStaggeredTileLayout( - crossAxisCount: crossAxisCount, - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - childCrossAxisExtent: childCrossAxisExtent, - scrollDirection: axisDirectionToAxis(constraints.axisDirection), - ); - } -} diff --git a/packages/dynamic_layouts/lib/src/wrap_layout.dart b/packages/dynamic_layouts/lib/src/wrap_layout.dart deleted file mode 100644 index 917680b2a74..00000000000 --- a/packages/dynamic_layouts/lib/src/wrap_layout.dart +++ /dev/null @@ -1,261 +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 'package:flutter/rendering.dart'; - -import 'base_grid_layout.dart'; - -// The model that tracks the current max size of the Sliver in the mainAxis and -// tracks if there is still space on the crossAxis. -class _RunMetrics { - _RunMetrics({ - required this.maxSliver, - required this.currentSizeUsed, - required this.scrollOffset, - }); - - /// The biggest sliver size for the current run. - double maxSliver; - - /// The current size that has been used in the current run. - double currentSizeUsed; - - /// The scroll offset in the current run. - double scrollOffset; -} - -/// A [DynamicSliverGridLayout] that uses dynamically sized tiles. -/// -/// Rather that providing a grid with a [DynamicSliverGridLayout] directly, instead -/// provide the grid a [SliverGridDelegate], which can compute a -/// [DynamicSliverGridLayout] given the current [SliverConstraints]. -/// -/// This layout is used by [SliverGridDelegateWithWrapping]. -/// -/// See also: -/// -/// * [SliverGridDelegateWithWrapping], which uses this layout. -/// * [DynamicSliverGridLayout], which represents an arbitrary dynamic tile layout. -/// * [DynamicSliverGridGeometry], which represents the size and position of a -/// single tile in a grid. -/// * [SliverGridDelegate.getLayout], which returns this object to describe the -/// delegate's layout. -/// * [RenderDynamicSliverGrid], which uses this class during its -/// [RenderDynamicSliverGrid.performLayout] method. -class SliverGridWrappingTileLayout extends DynamicSliverGridLayout { - /// Creates a layout that uses dynamic sized and spaced tiles. - /// - /// All of the arguments must not be null and must not be negative. - SliverGridWrappingTileLayout({ - required this.mainAxisSpacing, - required this.crossAxisSpacing, - required this.childMainAxisExtent, - required this.childCrossAxisExtent, - required this.crossAxisExtent, - required this.scrollDirection, - }) : assert(mainAxisSpacing >= 0), - assert(crossAxisSpacing >= 0), - assert(childMainAxisExtent >= 0), - assert(childCrossAxisExtent >= 0), - assert(crossAxisExtent >= 0), - assert(scrollDirection == Axis.horizontal || - scrollDirection == Axis.vertical); - - /// The direction in which the layout should be built. - final Axis scrollDirection; - - /// The extent of the child in the non-scrolling axis. - final double crossAxisExtent; - - /// The number of logical pixels between each child along the main axis. - final double mainAxisSpacing; - - /// The number of logical pixels between each child along the cross axis. - final double crossAxisSpacing; - - /// The number of pixels from the leading edge of one tile to the trailing - /// edge of the same tile in the main axis. - final double childMainAxisExtent; - - /// The number of pixels from the leading edge of one tile to the trailing - /// edge of the same tile in the cross axis. - final double childCrossAxisExtent; - - /// The model that is used internally to keep track of how much space is left - /// and how much has been used. - final List<_RunMetrics> _model = <_RunMetrics>[ - _RunMetrics(maxSliver: 0.0, currentSizeUsed: 0.0, scrollOffset: 0.0) - ]; - - // This method provides the initial constraints for the child to layout, - // and then it is updated with the final size later in - // updateGeometryForChildIndex. - @override - DynamicSliverGridGeometry getGeometryForChildIndex(int index) { - return DynamicSliverGridGeometry( - scrollOffset: 0, - crossAxisOffset: 0, - mainAxisExtent: childMainAxisExtent, - crossAxisExtent: childCrossAxisExtent, - ); - } - - @override - DynamicSliverGridGeometry updateGeometryForChildIndex( - int index, - Size childSize, - ) { - final double scrollOffset = _model.last.scrollOffset; - final double currentSizeUsed = _model.last.currentSizeUsed; - late final double addedSize; - - switch (scrollDirection) { - case Axis.vertical: - addedSize = currentSizeUsed + childSize.width + crossAxisSpacing; - case Axis.horizontal: - addedSize = currentSizeUsed + childSize.height + mainAxisSpacing; - } - - if (addedSize > crossAxisExtent && _model.last.currentSizeUsed > 0.0) { - switch (scrollDirection) { - case Axis.vertical: - _model.add( - _RunMetrics( - maxSliver: childSize.height + mainAxisSpacing, - currentSizeUsed: childSize.width + crossAxisSpacing, - scrollOffset: - scrollOffset + _model.last.maxSliver + mainAxisSpacing, - ), - ); - case Axis.horizontal: - _model.add( - _RunMetrics( - maxSliver: childSize.width + crossAxisSpacing, - currentSizeUsed: childSize.height + mainAxisSpacing, - scrollOffset: - scrollOffset + _model.last.maxSliver + crossAxisSpacing, - ), - ); - } - - return DynamicSliverGridGeometry( - scrollOffset: _model.last.scrollOffset, - crossAxisOffset: 0.0, - mainAxisExtent: childSize.height + mainAxisSpacing, - crossAxisExtent: childSize.width + crossAxisSpacing, - ); - } else { - _model.last.currentSizeUsed = addedSize; - } - - switch (scrollDirection) { - case Axis.vertical: - if (childSize.height + mainAxisSpacing > _model.last.maxSliver) { - _model.last.maxSliver = childSize.height + mainAxisSpacing; - } - case Axis.horizontal: - if (childSize.width + crossAxisSpacing > _model.last.maxSliver) { - _model.last.maxSliver = childSize.width + crossAxisSpacing; - } - } - - return DynamicSliverGridGeometry( - scrollOffset: scrollOffset, - crossAxisOffset: currentSizeUsed, - mainAxisExtent: childSize.height, - crossAxisExtent: childSize.width, - ); - } - - @override - bool reachedTargetScrollOffset(double targetOffset) { - return _model.last.scrollOffset > targetOffset; - } -} - -/// A [SliverGridDelegate] for creating grids that wrap variably sized tiles. -/// -/// For example, if the grid is vertical, this delegate will create a layout -/// where the children are laid out until they fill the horizontal axis and then -/// they continue in the next row. If the grid is horizontal, this delegate will -/// do the same but it will fill the vertical axis and will pass to another -/// column until it finishes. -/// -/// This delegate creates grids with different sized tiles. Tiles -/// can have fixed dimensions if [childCrossAxisExtent] or -/// [childMainAxisExtent] are provided. -/// -/// See also: -/// * [DynamicGridView.wrap], a constructor to use with this [SliverGridDelegate], -/// like `GridView.extent`. -/// * [DynamicGridView], which can use this delegate to control the layout of its -/// tiles. -/// * [RenderDynamicSliverGrid], which can use this delegate to control the -/// layout of its tiles. -class SliverGridDelegateWithWrapping extends SliverGridDelegate { - /// Create a delegate that wraps variably sized tiles. - /// - /// The children widgets are provided with loose constraints, and if any of the - /// extent parameters are set, the children are given tight constraints. - /// The way that children are made to have loose constraints is by assigning - /// the value of [double.infinity] to [childMainAxisExtent] and - /// [childCrossAxisExtent]. - /// To have same sized tiles with the wrapping, specify the [childCrossAxisExtent] - /// and the [childMainAxisExtent] to be the same size. Or only one of them to - /// be of a certain size in one of the axis. - const SliverGridDelegateWithWrapping({ - this.mainAxisSpacing = 0.0, - this.crossAxisSpacing = 0.0, - this.childCrossAxisExtent = double.infinity, - this.childMainAxisExtent = double.infinity, - }) : assert(mainAxisSpacing >= 0), - assert(crossAxisSpacing >= 0); - - /// The number of pixels from the leading edge of one tile to the trailing - /// edge of the same tile in the main axis. - /// - /// Defaults to [double.infinity] to provide the child with loose constraints. - final double childMainAxisExtent; - - /// The number of pixels from the leading edge of one tile to the trailing - /// edge of the same tile in the cross axis. - /// - /// Defaults to [double.infinity] to provide the child with loose constraints. - final double childCrossAxisExtent; - - /// The number of logical pixels between each child along the main axis. - /// - /// Defaults to 0.0 - final double mainAxisSpacing; - - /// The number of logical pixels between each child along the cross axis. - /// - /// Defaults to 0.0 - final double crossAxisSpacing; - - bool _debugAssertIsValid() { - assert(mainAxisSpacing >= 0.0); - assert(crossAxisSpacing >= 0.0); - return true; - } - - @override - SliverGridLayout getLayout(SliverConstraints constraints) { - assert(_debugAssertIsValid()); - return SliverGridWrappingTileLayout( - childMainAxisExtent: childMainAxisExtent, - childCrossAxisExtent: childCrossAxisExtent, - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - scrollDirection: axisDirectionToAxis(constraints.axisDirection), - crossAxisExtent: constraints.crossAxisExtent, - ); - } - - @override - bool shouldRelayout(SliverGridDelegateWithWrapping oldDelegate) { - return oldDelegate.mainAxisSpacing != mainAxisSpacing || - oldDelegate.crossAxisSpacing != crossAxisSpacing; - } -} diff --git a/packages/dynamic_layouts/pubspec.yaml b/packages/dynamic_layouts/pubspec.yaml deleted file mode 100644 index 4f2c39c6fc1..00000000000 --- a/packages/dynamic_layouts/pubspec.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: dynamic_layouts -description: Widgets for building dynamic grid layouts. -version: 0.0.1+1 -issue_tracker: https://github.com/flutter/flutter/labels/p%3A%20dynamic_layouts -repository: https://github.com/flutter/packages/tree/main/packages/dynamic_layouts -# Temporarily unpublished while in process of releasing full package in multiple stages. -publish_to: none - -environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" - -dependencies: - flutter: - sdk: flutter - -dev_dependencies: - flutter_test: - sdk: flutter diff --git a/packages/dynamic_layouts/test/base_grid_layout_test.dart b/packages/dynamic_layouts/test/base_grid_layout_test.dart deleted file mode 100644 index 59e6bd5564b..00000000000 --- a/packages/dynamic_layouts/test/base_grid_layout_test.dart +++ /dev/null @@ -1,102 +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 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('DynamicSliverGridGeometry returns tight constraints for finite extents', - () { - const DynamicSliverGridGeometry geometry = DynamicSliverGridGeometry( - scrollOffset: 0, - crossAxisOffset: 0, - crossAxisExtent: 150.0, - mainAxisExtent: 50.0, - ); - - // Vertical - SliverConstraints sliverConstraints = const SliverConstraints( - axisDirection: AxisDirection.down, - growthDirection: GrowthDirection.forward, - userScrollDirection: ScrollDirection.forward, - scrollOffset: 0, - precedingScrollExtent: 0, - overlap: 0, - remainingPaintExtent: 600, - crossAxisExtent: 300, - crossAxisDirection: AxisDirection.left, - viewportMainAxisExtent: 1000, - remainingCacheExtent: 600, - cacheOrigin: 0.0, - ); - BoxConstraints constraints = geometry.getBoxConstraints(sliverConstraints); - expect(constraints, BoxConstraints.tight(const Size(150.0, 50.0))); - - // Horizontal - sliverConstraints = const SliverConstraints( - axisDirection: AxisDirection.left, - growthDirection: GrowthDirection.forward, - userScrollDirection: ScrollDirection.forward, - scrollOffset: 0, - precedingScrollExtent: 0, - overlap: 0, - remainingPaintExtent: 600, - crossAxisExtent: 300, - crossAxisDirection: AxisDirection.down, - viewportMainAxisExtent: 1000, - remainingCacheExtent: 600, - cacheOrigin: 0.0, - ); - constraints = geometry.getBoxConstraints(sliverConstraints); - expect(constraints, BoxConstraints.tight(const Size(50.0, 150.0))); - }); - - test( - 'DynamicSliverGridGeometry returns loose constraints for infinite extents', - () { - const DynamicSliverGridGeometry geometry = DynamicSliverGridGeometry( - scrollOffset: 0, - crossAxisOffset: 0, - mainAxisExtent: double.infinity, - crossAxisExtent: double.infinity, - ); - - // Vertical - SliverConstraints sliverConstraints = const SliverConstraints( - axisDirection: AxisDirection.down, - growthDirection: GrowthDirection.forward, - userScrollDirection: ScrollDirection.forward, - scrollOffset: 0, - precedingScrollExtent: 0, - overlap: 0, - remainingPaintExtent: 600, - crossAxisExtent: 300, - crossAxisDirection: AxisDirection.left, - viewportMainAxisExtent: 1000, - remainingCacheExtent: 600, - cacheOrigin: 0.0, - ); - BoxConstraints constraints = geometry.getBoxConstraints(sliverConstraints); - expect(constraints, const BoxConstraints()); - - // Horizontal - sliverConstraints = const SliverConstraints( - axisDirection: AxisDirection.left, - growthDirection: GrowthDirection.forward, - userScrollDirection: ScrollDirection.forward, - scrollOffset: 0, - precedingScrollExtent: 0, - overlap: 0, - remainingPaintExtent: 600, - crossAxisExtent: 300, - crossAxisDirection: AxisDirection.down, - viewportMainAxisExtent: 1000, - remainingCacheExtent: 600, - cacheOrigin: 0.0, - ); - constraints = geometry.getBoxConstraints(sliverConstraints); - expect(constraints, const BoxConstraints()); - }); -} diff --git a/packages/dynamic_layouts/test/dynamic_grid_test.dart b/packages/dynamic_layouts/test/dynamic_grid_test.dart deleted file mode 100644 index a4005d3c9e4..00000000000 --- a/packages/dynamic_layouts/test/dynamic_grid_test.dart +++ /dev/null @@ -1,151 +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 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('DynamicGridView works with simple layout', - (WidgetTester tester) async { - // Can have no children - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView( - gridDelegate: TestDelegate(crossAxisCount: 2), - ), - ), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView( - gridDelegate: TestDelegate(crossAxisCount: 2), - children: List.generate( - 50, - (int index) => SizedBox.square( - dimension: TestSimpleLayout.childExtent, - child: Text('Index $index'), - ), - ), - ), - ), - ), - ); - - // Only the visible tiles have been laid out. - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(50.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(0.0, 50.0)); - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - testWidgets('DynamicGridView.builder works with simple layout', - (WidgetTester tester) async { - // Only a few number of tiles - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: TestDelegate(crossAxisCount: 2), - itemCount: 3, - itemBuilder: (BuildContext context, int index) { - return SizedBox.square( - dimension: TestSimpleLayout.childExtent, - child: Text('Index $index'), - ); - }, - ), - ), - ), - ); - - // Only the visible tiles have been laid out, up to itemCount. - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(50.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(0.0, 50.0)); - expect(find.text('Index 3'), findsNothing); - expect(find.text('Index 4'), findsNothing); - expect(find.text('Index 5'), findsNothing); - - // Infinite number of tiles - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: TestDelegate(crossAxisCount: 2), - itemBuilder: (BuildContext context, int index) { - return SizedBox.square( - dimension: TestSimpleLayout.childExtent, - child: Text('Index $index'), - ); - }, - ), - ), - ), - ); - - // Only the visible tiles have been laid out. - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(50.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(0.0, 50.0)); - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); -} - -class TestSimpleLayout extends DynamicSliverGridLayout { - TestSimpleLayout({ - required this.crossAxisCount, - }); - - final int crossAxisCount; - static const double childExtent = 50.0; - - @override - DynamicSliverGridGeometry getGeometryForChildIndex(int index) { - final double crossAxisStart = (index % crossAxisCount) * childExtent; - return DynamicSliverGridGeometry( - scrollOffset: (index ~/ crossAxisCount) * childExtent, - crossAxisOffset: crossAxisStart, - mainAxisExtent: childExtent, - crossAxisExtent: childExtent, - ); - } - - @override - bool reachedTargetScrollOffset(double targetOffset) => true; - - @override - DynamicSliverGridGeometry updateGeometryForChildIndex( - int index, - Size childSize, - ) { - return getGeometryForChildIndex(index); - } -} - -class TestDelegate extends SliverGridDelegateWithFixedCrossAxisCount { - TestDelegate({required super.crossAxisCount}); - - @override - DynamicSliverGridLayout getLayout(SliverConstraints constraints) { - return TestSimpleLayout(crossAxisCount: crossAxisCount); - } -} diff --git a/packages/dynamic_layouts/test/staggered_layout_test.dart b/packages/dynamic_layouts/test/staggered_layout_test.dart deleted file mode 100644 index e86b2b4e3db..00000000000 --- a/packages/dynamic_layouts/test/staggered_layout_test.dart +++ /dev/null @@ -1,436 +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 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('DynamicGridView', () { - testWidgets( - 'DynamicGridView works when using DynamicSliverGridDelegateWithFixedCrossAxisCount', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(400, 100); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView( - gridDelegate: - const DynamicSliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - ), - children: List.generate( - 50, - (int index) => SizedBox( - height: index % 2 * 20 + 20, - child: Text('Index $index'), - ), - ), - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(100.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(200.0, 0.0)); - expect(find.text('Index 3'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 3')), const Offset(300.0, 0.0)); - expect(find.text('Index 4'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 4')), const Offset(0.0, 20.0)); - - expect(find.text('Index 14'), findsNothing); - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - - testWidgets( - 'DynamicGridView works when using DynamicSliverGridDelegateWithMaxCrossAxisExtent', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(440, 100); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView( - gridDelegate: - const DynamicSliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 100, - ), - children: List.generate( - 50, - (int index) => SizedBox( - height: index % 2 * 20 + 20, - child: Text('Index $index'), - ), - ), - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(88.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(176.0, 0.0)); - expect(find.text('Index 3'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 3')), const Offset(264.0, 0.0)); - expect(find.text('Index 4'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 4')), const Offset(352.0, 0.0)); - - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - }); - group('DynamicGridView.staggered', () { - testWidgets('DynamicGridView.staggered works with simple layout', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(400, 100); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.staggered( - crossAxisCount: 4, - children: List.generate( - 50, - (int index) => SizedBox( - height: index % 2 * 50 + 20, - child: Text('Index $index'), - ), - ), - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(100.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(200.0, 0.0)); - expect(find.text('Index 3'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 3')), const Offset(300.0, 0.0)); - expect(find.text('Index 4'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 4')), const Offset(0.0, 20.0)); - expect(find.text('Index 5'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 5')), - const Offset(200.0, 20.0), - ); - expect(find.text('Index 6'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 6')), const Offset(0.0, 40.0)); - expect(find.text('Index 7'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 7')), const Offset(0.0, 60.0)); - - expect(find.text('Index 12'), findsNothing); // 100 - 120 - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - testWidgets('DynamicGridView.staggered works with a horizontal grid', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(100, 500); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.staggered( - crossAxisCount: 4, - scrollDirection: Axis.horizontal, - children: List.generate( - 50, - (int index) => SizedBox( - width: index % 3 * 50 + 20, - child: Text('Index $index'), - ), - ), - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(0.0, 125.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(0.0, 250.0)); - expect(find.text('Index 3'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 3')), const Offset(0.0, 375.0)); - expect(find.text('Index 4'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 4')), const Offset(20.0, 0.0)); - expect(find.text('Index 5'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 5')), - const Offset(20.0, 375.0), - ); - expect(find.text('Index 6'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 6')), - const Offset(70.0, 125.0), - ); - expect(find.text('Index 7'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 7')), - const Offset(90.0, 0.0), - ); - - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - testWidgets('DynamicGridView.staggered works with a reversed grid', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(600, 200); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.staggered( - crossAxisCount: 4, - reverse: true, - children: List.generate( - 50, - (int index) => SizedBox( - height: index % 3 * 50 + 20, - child: Text('Index $index'), - ), - ), - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 0')), - const Offset(0.0, 200.0), - ); - expect(find.text('Index 1'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 1')), - const Offset(150.0, 200.0), - ); - expect(find.text('Index 2'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 2')), - const Offset(300.0, 200.0), - ); - expect(find.text('Index 3'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 3')), - const Offset(450.0, 200.0), - ); - expect(find.text('Index 4'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 4')), - const Offset(0.0, 180.0), - ); - expect(find.text('Index 5'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 5')), - const Offset(450.0, 180.0), - ); - expect(find.text('Index 6'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 6')), - const Offset(150.0, 130.0), - ); - expect(find.text('Index 7'), findsOneWidget); - expect( - tester.getBottomLeft(find.text('Index 7')), - const Offset(0.0, 110.0), - ); - - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - - testWidgets('DynamicGridView.staggered deletes children appropriately', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(600, 1000); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - final List children = List.generate( - 50, - (int index) => SizedBox( - height: index % 3 * 50 + 20, - child: Text('Index $index'), - ), - ); - late StateSetter stateSetter; - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - stateSetter = setState; - return DynamicGridView.staggered( - maxCrossAxisExtent: 150, - children: [...children], - ); - }), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 7'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 7')), const Offset(0.0, 90.0)); - expect(find.text('Index 8'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 8')), - const Offset(150.0, 90.0), - ); - expect(find.text('Index 27'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 27')), - const Offset(300.0, 420.0), - ); - expect(find.text('Index 28'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 28')), - const Offset(300.0, 440.0), - ); - expect(find.text('Index 32'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 32')), - const Offset(300.0, 510.0), - ); - expect(find.text('Index 33'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 33')), - const Offset(150.0, 540.0), - ); - - stateSetter(() { - children.removeAt(0); - }); - - await tester.pump(); - expect(find.text('Index 0'), findsNothing); - - expect( - tester.getTopLeft(find.text('Index 8')), - const Offset(0.0, 90.0), - ); - expect( - tester.getTopLeft(find.text('Index 28')), - const Offset(150.0, 440.0), - ); - expect( - tester.getTopLeft(find.text('Index 33')), - const Offset(0.0, 540.0), - ); - }); - }); - group('DynamicGridView.builder', () { - testWidgets('DynamicGridView.builder works with a staggered layout', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(400, 100); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: - const DynamicSliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - ), - itemBuilder: (BuildContext context, int index) => SizedBox( - height: index % 2 * 50 + 20, - child: Text('Index $index'), - ), - itemCount: 50, - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 0')), Offset.zero); - expect(find.text('Index 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 1')), const Offset(100.0, 0.0)); - expect(find.text('Index 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 2')), const Offset(200.0, 0.0)); - expect(find.text('Index 3'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 3')), const Offset(300.0, 0.0)); - expect(find.text('Index 4'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 4')), const Offset(0.0, 20.0)); - expect(find.text('Index 5'), findsOneWidget); - expect( - tester.getTopLeft(find.text('Index 5')), - const Offset(200.0, 20.0), - ); - expect(find.text('Index 6'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 6')), const Offset(0.0, 40.0)); - expect(find.text('Index 7'), findsOneWidget); - expect(tester.getTopLeft(find.text('Index 7')), const Offset(0.0, 60.0)); - - expect(find.text('Index 12'), findsNothing); // 100 - 120 - expect(find.text('Index 47'), findsNothing); - expect(find.text('Index 48'), findsNothing); - expect(find.text('Index 49'), findsNothing); - }); - - testWidgets( - 'DynamicGridView.builder works with an infinite grid using a staggered layout', - (WidgetTester tester) async { - tester.view.physicalSize = const Size(400, 100); - tester.view.devicePixelRatio = 1.0; - addTearDown(tester.view.reset); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: - const DynamicSliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - ), - itemBuilder: (BuildContext context, int index) => SizedBox( - height: index % 2 * 50 + 20, - child: Text('Index $index'), - ), - ), - ), - ), - ); - - expect(find.text('Index 0'), findsOneWidget); - expect(find.text('Index 1'), findsOneWidget); - expect(find.text('Index 2'), findsOneWidget); - await tester.scrollUntilVisible(find.text('Index 500'), 500.0); - await tester.pumpAndSettle(); - expect(find.text('Index 501'), findsOneWidget); - expect(find.text('Index 502'), findsOneWidget); - }); - }); -} diff --git a/packages/dynamic_layouts/test/wrap_layout_test.dart b/packages/dynamic_layouts/test/wrap_layout_test.dart deleted file mode 100644 index 31daa8b3476..00000000000 --- a/packages/dynamic_layouts/test/wrap_layout_test.dart +++ /dev/null @@ -1,636 +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 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets( - 'DynamicGridView generates children and checks if they are layed out', - (WidgetTester tester) async { - final List children = List.generate( - 10, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 95 : 180, - child: Text('Item $index'), - ), - ); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView( - gridDelegate: const SliverGridDelegateWithWrapping(), - children: children, - ), - ), - ), - ); - - // Check that the children are in the tree - for (int i = 0; i < 10; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - // Check that the children are in the right position - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(95.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(275.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(370.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 4')), const Offset(550.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 5')), const Offset(0.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 6')), const Offset(180.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 7')), const Offset(275.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 8')), const Offset(455.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 9')), const Offset(550.0, 100.0)); - }); - - testWidgets( - 'Test for wrap that generates children and checks if they are layed out', - (WidgetTester tester) async { - final List children = List.generate( - 10, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 95 : 180, - child: Text('Item $index'), - ), - ); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.wrap( - children: children, - ), - ), - ), - ); - for (int i = 0; i < 10; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - // Check that the children are in the right position - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(95.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(275.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(370.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 4')), const Offset(550.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 5')), const Offset(0.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 6')), const Offset(180.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 7')), const Offset(275.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 8')), const Offset(455.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 9')), const Offset(550.0, 100.0)); - }); - - testWidgets('Test for wrap to be laying child dynamically', - (WidgetTester tester) async { - final List children = List.generate( - 20, - (int index) => SizedBox( - height: index.isEven ? 1000 : 50, - width: index.isEven ? 95 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - itemCount: children.length, - gridDelegate: const SliverGridDelegateWithWrapping(), - itemBuilder: (BuildContext context, int index) => children[index], - ), - ), - ), - ); - for (int i = 0; i < 5; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - // Check that the children are in the right position - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(95.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(275.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(370.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 4')), const Offset(550.0, 0.0)); - expect(find.text('Item 5'), findsNothing); - await tester.scrollUntilVisible(find.text('Item 19'), 500.0); - await tester.pumpAndSettle(); - - expect(find.text('Item 18'), findsOneWidget); - expect(tester.getTopLeft(find.text('Item 18')), const Offset(455.0, 0.0)); - - expect(find.text('Item 0'), findsNothing); - expect(find.text('Item 1'), findsNothing); - expect(find.text('Item 2'), findsNothing); - expect(find.text('Item 3'), findsNothing); - }); - - testWidgets( - 'Test for DynamicGridView.wrap to scrollDirection Axis.horizontal', - (WidgetTester tester) async { - final List children = List.generate( - 20, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.wrap( - scrollDirection: Axis.horizontal, - children: children, - ), - ), - ), - ); - for (int i = 0; i < 20; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - // Check that the children are in the right position - double dy = 0, dx = 0; - for (int i = 0; i < 20; i++) { - if (dy >= 600.0) { - dy = 0.0; - dx += 180.0; - } - expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy)); - dy += i.isEven ? 100 : 50; - } - }); - - testWidgets('Test DynamicGridView.builder for GridView.reverse to true', - (WidgetTester tester) async { - final List children = List.generate( - 10, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - reverse: true, - itemCount: children.length, - gridDelegate: const SliverGridDelegateWithWrapping(), - itemBuilder: (BuildContext context, int index) => children[index], - ), - ), - ), - ); - for (int i = 0; i < 10; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - double dx = 0.0, dy = 600.0; - for (int i = 0; i < 10; i++) { - if (dx >= 600.0) { - dx = 0.0; - dy -= 100.0; - } - expect(tester.getBottomLeft(find.text('Item $i')), Offset(dx, dy)); - dx += i.isEven ? 100 : 180; - } - }); - - testWidgets('DynamicGridView.wrap for GridView.reverse to true', - (WidgetTester tester) async { - final List children = List.generate( - 20, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.wrap( - reverse: true, - children: children, - ), - ), - ), - ); - for (int i = 0; i < 20; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - // Check that the children are in the right position - double dx = 0.0, dy = 600.0; - for (int i = 0; i < 20; i++) { - if (dx >= 600.0) { - dx = 0.0; - dy -= 100.0; - } - expect(tester.getBottomLeft(find.text('Item $i')), Offset(dx, dy)); - dx += i.isEven ? 100 : 180; - } - }); - - testWidgets('DynamicGridView.wrap dismiss keyboard onDrag test', - (WidgetTester tester) async { - final List focusNodes = - List.generate(50, (int i) => FocusNode()); - - await tester.pumpWidget( - textFieldBoilerplate( - child: GridView.extent( - padding: EdgeInsets.zero, - maxCrossAxisExtent: 300, - keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, - children: focusNodes.map((FocusNode focusNode) { - return Container( - height: 50, - color: Colors.green, - child: TextField( - focusNode: focusNode, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ); - }).toList(), - ), - ), - ); - - final Finder finder = find.byType(TextField).first; - final TextField textField = tester.widget(finder); - await tester.showKeyboard(finder); - expect(textField.focusNode!.hasFocus, isTrue); - - await tester.drag(finder, const Offset(0.0, -40.0)); - await tester.pumpAndSettle(); - expect(textField.focusNode!.hasFocus, isFalse); - }); - - testWidgets('ChildMainAxisExtent & childCrossAxisExtent are respected', - (WidgetTester tester) async { - final List children = List.generate( - 10, - (int index) => SizedBox( - key: Key(index.toString()), - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: const SliverGridDelegateWithWrapping( - childMainAxisExtent: 150, - childCrossAxisExtent: 200, - ), - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - ), - ), - ), - ); - - for (int i = 0; i < 10; i++) { - final Size sizeOfCurrent = tester.getSize(find.byKey(Key('$i'))); - expect(sizeOfCurrent.width, equals(200)); - expect(sizeOfCurrent.height, equals(150)); - } - // Check that the children are in the right position - double dy = 0, dx = 0; - for (int i = 0; i < 10; i++) { - if (dx > 600.0) { - dx = 0.0; - dy += 150.0; - } - expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy)); - dx += 200; - } - }); - - testWidgets('ChildMainAxisExtent is respected', (WidgetTester tester) async { - final List children = List.generate( - 10, - (int index) => SizedBox( - key: Key(index.toString()), - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: const SliverGridDelegateWithWrapping( - childMainAxisExtent: 200, - ), - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - ), - ), - ), - ); - - for (int i = 0; i < 10; i++) { - final Size sizeOfCurrent = tester.getSize(find.byKey(Key('$i'))); - expect(sizeOfCurrent.height, equals(200)); - } - // Check that the children are in the right position - double dy = 0, dx = 0; - for (int i = 0; i < 10; i++) { - if (dx >= 600.0) { - dx = 0.0; - dy += 200.0; - } - expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy)); - dx += i.isEven ? 100 : 180; - } - }); - - testWidgets('ChildCrossAxisExtent is respected', (WidgetTester tester) async { - final List children = List.generate( - 10, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - key: Key(index.toString()), - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - gridDelegate: const SliverGridDelegateWithWrapping( - childCrossAxisExtent: 150, - ), - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - ), - ), - ), - ); - - for (int i = 0; i < 10; i++) { - final Size sizeOfCurrent = tester.getSize(find.byKey(Key('$i'))); - expect(sizeOfCurrent.width, equals(150)); - } - // Check that the children are in the right position - double dy = 0, dx = 0; - for (int i = 0; i < 10; i++) { - if (dx >= 750.0) { - dx = 0.0; - dy += 100.0; - } - expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy)); - dx += 150; - } - }); - - testWidgets('Test wrap to see nothing affected if elements are deleted.', - (WidgetTester tester) async { - late StateSetter stateSetter; - final List children = List.generate( - 10, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - stateSetter = setState; - return DynamicGridView.builder( - gridDelegate: const SliverGridDelegateWithWrapping(), - itemCount: children.length, - itemBuilder: (BuildContext context, int index) => children[index], - ); - }), - ), - ), - ); - // See if the children are in the tree. - for (int i = 0; i < 10; i++) { - expect(find.text('Item $i'), findsOneWidget); - } - // See if they are layed properly. - double dx = 0.0, dy = 0.0; - for (int i = 0; i < 10; i++) { - if (dx >= 600) { - dx = 0.0; - dy += 100; - } - expect(tester.getTopLeft(find.text('Item $i')), Offset(dx, dy)); - dx += i.isEven ? 100 : 180; - } - stateSetter(() { - // Remove children - children.removeAt(0); - children.removeAt(8); - children.removeAt(5); - }); - - await tester.pump(); - - // See if the proper widgets are in the tree. - expect(find.text('Item 0'), findsNothing); - expect(find.text('Item 6'), findsNothing); - expect(find.text('Item 9'), findsNothing); - expect(find.text('Item 1'), findsOneWidget); - expect(find.text('Item 2'), findsOneWidget); - expect(find.text('Item 3'), findsOneWidget); - expect(find.text('Item 4'), findsOneWidget); - expect(find.text('Item 5'), findsOneWidget); - expect(find.text('Item 7'), findsOneWidget); - expect(find.text('Item 8'), findsOneWidget); - - // See if the proper widgets are in the tree. - expect(tester.getTopLeft(find.text('Item 1')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(180.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(280.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 4')), const Offset(460.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 5')), const Offset(560.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 7')), const Offset(0.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 8')), const Offset(180.0, 100.0)); - }); - - testWidgets('Test wrap in Axis.vertical direction', - (WidgetTester tester) async { - final List children = List.generate( - 5, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.builder( - itemCount: children.length, - gridDelegate: const SliverGridDelegateWithWrapping(), - itemBuilder: (BuildContext context, int index) => children[index], - ), - ), - ), - ); - - // Change the size of the screen - await tester.binding.setSurfaceSize(const Size(500, 100)); - await tester.pumpAndSettle(); - expect(find.text('Item 0'), findsOneWidget); - expect(find.text('Item 1'), findsOneWidget); - expect(find.text('Item 2'), findsOneWidget); - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(100.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(280.0, 0.0)); - expect(find.text('Item 3'), findsNothing); - expect(find.text('Item 4'), findsNothing); - await tester.binding.setSurfaceSize(const Size(560, 100)); - await tester.pumpAndSettle(); - expect(find.text('Item 3'), findsOneWidget); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(380.0, 0.0)); - expect(find.text('Item 4'), findsNothing); - await tester.binding.setSurfaceSize(const Size(280, 100)); - // resets the screen to its original size after the test end - addTearDown(tester.view.resetPhysicalSize); - await tester.pumpAndSettle(); - expect(find.text('Item 0'), findsOneWidget); - expect(find.text('Item 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(100.0, 0.0)); - expect(find.text('Item 2'), findsNothing); - expect(find.text('Item 3'), findsNothing); - expect(find.text('Item 4'), findsNothing); - }); - - testWidgets('Test wrap in Axis.horizontal direction', - (WidgetTester tester) async { - final List children = List.generate( - 5, - (int index) => SizedBox( - height: index.isEven ? 100 : 50, - width: index.isEven ? 100 : 180, - child: Text('Item $index'), - ), - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: DynamicGridView.wrap( - scrollDirection: Axis.horizontal, - children: children, - ), - ), - ), - ); - - // Change the size of the screen - await tester.binding.setSurfaceSize(const Size(180, 150)); - await tester.pumpAndSettle(); - - expect(find.text('Item 0'), findsOneWidget); - expect(find.text('Item 1'), findsOneWidget); - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(0.0, 100.0)); - - expect(find.text('Item 2'), findsNothing); - expect(find.text('Item 3'), findsNothing); - - await tester.binding.setSurfaceSize(const Size(180, 400)); - await tester.pumpAndSettle(); - - expect(find.text('Item 0'), findsOneWidget); - expect(find.text('Item 1'), findsOneWidget); - expect(find.text('Item 2'), findsOneWidget); - expect(find.text('Item 3'), findsOneWidget); - expect(find.text('Item 4'), findsOneWidget); - - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(0.0, 100.0)); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(0.0, 150.0)); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(0.0, 250.0)); - expect(tester.getTopLeft(find.text('Item 4')), const Offset(0.0, 300.0)); - - await tester.binding.setSurfaceSize(const Size(560, 100)); - // resets the screen to its original size after the test end - addTearDown(tester.view.resetPhysicalSize); - await tester.pumpAndSettle(); - - expect(find.text('Item 0'), findsOneWidget); - expect(find.text('Item 1'), findsOneWidget); - expect(find.text('Item 2'), findsOneWidget); - expect(find.text('Item 3'), findsOneWidget); - expect(find.text('Item 4'), findsNothing); - - expect(tester.getTopLeft(find.text('Item 0')), Offset.zero); - expect(tester.getTopLeft(find.text('Item 1')), const Offset(100.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 2')), const Offset(280.0, 0.0)); - expect(tester.getTopLeft(find.text('Item 3')), const Offset(380.0, 0.0)); - }); -} - -Widget textFieldBoilerplate({required Widget child}) { - return MaterialApp( - home: Localizations( - locale: const Locale('en', 'US'), - delegates: >[ - WidgetsLocalizationsDelegate(), - MaterialLocalizationsDelegate(), - ], - child: Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(size: Size(800.0, 600.0)), - child: Center( - child: Material( - child: child, - ), - ), - ), - ), - ), - ); -} - -class MaterialLocalizationsDelegate - extends LocalizationsDelegate { - @override - bool isSupported(Locale locale) => true; - - @override - Future load(Locale locale) => - DefaultMaterialLocalizations.load(locale); - - @override - bool shouldReload(MaterialLocalizationsDelegate old) => false; -} - -class WidgetsLocalizationsDelegate - extends LocalizationsDelegate { - @override - bool isSupported(Locale locale) => true; - - @override - Future load(Locale locale) => - DefaultWidgetsLocalizations.load(locale); - - @override - bool shouldReload(WidgetsLocalizationsDelegate old) => false; -} diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md index 95cc21d686e..1f573dcbd3e 100644 --- a/packages/espresso/CHANGELOG.md +++ b/packages/espresso/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.3.1 + +* Updates espresso version to 3.6.1 +* Updates androidx.test to 1.6.1 + +## 0.3.0+10 + +* Removes additional references to v1 Android embedding. + +## 0.3.0+9 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 0.3.0+8 * Updates minSdkVersion to 19. diff --git a/packages/espresso/README.md b/packages/espresso/README.md index 95c72e33442..0e0d7880a9d 100644 --- a/packages/espresso/README.md +++ b/packages/espresso/README.md @@ -19,9 +19,9 @@ Add the following dependencies in android/app/build.gradle: ```groovy dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation "com.google.truth:truth:1.0" - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + testImplementation "com.google.truth:truth:1.1.3" + androidTestImplementation 'androidx.test:runner:1.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' api 'androidx.test:core:1.2.0' } ``` diff --git a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterViewAction.java b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterViewAction.java index 96cc9d7aead..d18ced46026 100644 --- a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterViewAction.java +++ b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterViewAction.java @@ -38,6 +38,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.annotation.Nonnull; import okhttp3.OkHttpClient; import org.hamcrest.Matcher; @@ -97,14 +98,19 @@ public String getDescription() { @ExperimentalTestApi @Override - public void perform(UiController uiController, View flutterView) { + public void perform(UiController uiController, View view) { + checkNotNull(view, "The Flutter View instance cannot be null."); + if (!(view instanceof FlutterView)) { + throw new FlutterProtocolException( + String.format("This is not a Flutter View instance [id: %d].", view.getId())); + } + FlutterView flutterView = (FlutterView) view; // There could be a gap between when the Flutter view is available in the view hierarchy and the // engine & Dart isolates are actually up and running. Check whether the first frame has been // rendered before proceeding in an unblocking way. loopUntilFlutterViewRendered(flutterView, uiController); // The url {@code FlutterNativeView} returns is the http url that the Dart VM Observatory http // server serves at. Need to convert to the one that the WebSocket uses. - URI dartVmServiceProtocolUrl = DartVmServiceUtil.getServiceProtocolUri(FlutterJNI.getVMServiceUri()); String isolateId = DartVmServiceUtil.getDartIsolateId(flutterView); @@ -171,7 +177,8 @@ public T waitUntilCompleted(long timeout, TimeUnit unit) return resultFuture.get(timeout, unit); } - private static void loopUntilFlutterViewRendered(View flutterView, UiController uiController) { + private static void loopUntilFlutterViewRendered( + @Nonnull FlutterView flutterView, UiController uiController) { FlutterViewRenderedIdlingResource idlingResource = new FlutterViewRenderedIdlingResource(flutterView); try { @@ -188,12 +195,12 @@ private static void loopUntilFlutterViewRendered(View flutterView, UiController */ static final class FlutterViewRenderedIdlingResource implements IdlingResource { - private final View flutterView; + private final FlutterView flutterView; // Written from main thread, read from any thread. private volatile ResourceCallback resourceCallback; - FlutterViewRenderedIdlingResource(View flutterView) { - this.flutterView = checkNotNull(flutterView); + FlutterViewRenderedIdlingResource(@Nonnull FlutterView flutterView) { + this.flutterView = flutterView; } @Override @@ -201,18 +208,9 @@ public String getName() { return FlutterViewRenderedIdlingResource.class.getSimpleName(); } - @SuppressWarnings("deprecation") @Override public boolean isIdleNow() { - boolean isIdle = false; - if (flutterView instanceof FlutterView) { - isIdle = ((FlutterView) flutterView).hasRenderedFirstFrame(); - } else if (flutterView instanceof io.flutter.view.FlutterView) { - isIdle = ((io.flutter.view.FlutterView) flutterView).hasRenderedFirstFrame(); - } else { - throw new FlutterProtocolException( - String.format("This is not a Flutter View instance [id: %d].", flutterView.getId())); - } + boolean isIdle = flutterView.hasRenderedFirstFrame(); if (isIdle) { resourceCallback.onTransitionToIdle(); } diff --git a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/SyntheticAction.java b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/SyntheticAction.java index 41af3e99dfd..8c18b2dfb37 100644 --- a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/SyntheticAction.java +++ b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/SyntheticAction.java @@ -20,7 +20,7 @@ * action that's performed via Flutter engine. It's supposed to be used for complex interactions or * those that are brittle if performed through Android system. Most of the actions should be * associated with a {@link WidgetMatcher}, but some may not, e.g. an action that checks the - * rendering status of the entire {@link io.flutter.view.FlutterView}. + * rendering status of the entire {@link io.flutter.embedding.android.FlutterView}. */ @Beta public abstract class SyntheticAction { diff --git a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/DartVmServiceUtil.java b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/DartVmServiceUtil.java index 63c62c4f504..06110350706 100644 --- a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/DartVmServiceUtil.java +++ b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/DartVmServiceUtil.java @@ -4,17 +4,17 @@ package androidx.test.espresso.flutter.internal.protocol.impl; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import android.util.Log; -import android.view.View; +import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import javax.annotation.Nonnull; /** Util class for dealing with Dart VM service protocols. */ public final class DartVmServiceUtil { @@ -59,8 +59,7 @@ public static URI getServiceProtocolUri(String observatoryUrl) { } /** Gets the Dart isolate ID for the given {@code flutterView}. */ - public static String getDartIsolateId(View flutterView) { - checkNotNull(flutterView, "The Flutter View instance cannot be null."); + public static String getDartIsolateId(FlutterView flutterView) { String uiIsolateId = getDartExecutor(flutterView).getIsolateServiceId(); Log.d( TAG, @@ -71,25 +70,13 @@ public static String getDartIsolateId(View flutterView) { } /** Gets the Dart executor for the given {@code flutterView}. */ - @SuppressWarnings("deprecation") - public static DartExecutor getDartExecutor(View flutterView) { - checkNotNull(flutterView, "The Flutter View instance cannot be null."); - // Flutter's embedding is in the phase of rewriting/refactoring. Let's be compatible with both - // the old and the new FlutterView classes. - if (flutterView instanceof io.flutter.view.FlutterView) { - return ((io.flutter.view.FlutterView) flutterView).getDartExecutor(); - } else if (flutterView instanceof io.flutter.embedding.android.FlutterView) { - FlutterEngine flutterEngine = - ((io.flutter.embedding.android.FlutterView) flutterView).getAttachedFlutterEngine(); - if (flutterEngine == null) { - throw new FlutterProtocolException( - String.format( - "No Flutter engine attached to the Flutter view [id: %d].", flutterView.getId())); - } - return flutterEngine.getDartExecutor(); - } else { + public static DartExecutor getDartExecutor(@Nonnull FlutterView flutterView) { + FlutterEngine flutterEngine = flutterView.getAttachedFlutterEngine(); + if (flutterEngine == null) { throw new FlutterProtocolException( - String.format("This is not a Flutter View instance [id: %d].", flutterView.getId())); + String.format( + "No Flutter engine attached to the Flutter view [id: %d].", flutterView.getId())); } + return flutterEngine.getDartExecutor(); } } diff --git a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/FlutterMatchers.java b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/FlutterMatchers.java index 9db88665f8e..4c87b51e3ce 100644 --- a/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/FlutterMatchers.java +++ b/packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/FlutterMatchers.java @@ -96,11 +96,9 @@ public void describeTo(Description description) { description.appendText("is a FlutterView"); } - @SuppressWarnings("deprecation") @Override public boolean matchesSafely(View flutterView) { - return flutterView instanceof FlutterView - || (flutterView instanceof io.flutter.view.FlutterView); + return flutterView instanceof FlutterView; } } } diff --git a/packages/espresso/android/src/main/java/com/example/espresso/EspressoPlugin.java b/packages/espresso/android/src/main/java/com/example/espresso/EspressoPlugin.java index 6c8620b3ca1..213e83c22f7 100644 --- a/packages/espresso/android/src/main/java/com/example/espresso/EspressoPlugin.java +++ b/packages/espresso/android/src/main/java/com/example/espresso/EspressoPlugin.java @@ -20,21 +20,6 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin channel.setMethodCallHandler(new EspressoPlugin()); } - // This static function is optional and equivalent to onAttachedToEngine. It supports the old - // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting - // plugin registration via this function while apps migrate to use the new Android APIs - // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. - // - // It is encouraged to share logic between onAttachedToEngine and registerWith to keep - // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called - // depending on the user's project. onAttachedToEngine or registerWith must both be defined - // in the same class. - @SuppressWarnings("deprecation") - public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - final MethodChannel channel = new MethodChannel(registrar.messenger(), "espresso"); - channel.setMethodCallHandler(new EspressoPlugin()); - } - @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if (call.method.equals("getPlatformVersion")) { diff --git a/packages/espresso/example/android/app/build.gradle b/packages/espresso/example/android/app/build.gradle index 6deacadd65c..be165715ca2 100644 --- a/packages/espresso/example/android/app/build.gradle +++ b/packages/espresso/example/android/app/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { minSdkVersion flutter.minSdkVersion - targetSdkVersion 29 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -62,28 +62,28 @@ dependencies { implementation "androidx.multidex:multidex:2.0.1" // Core library - api 'androidx.test:core:1.2.0' + api 'androidx.test:core:1.6.1' // AndroidJUnitRunner and JUnit Rules - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test:rules:1.1.0' + androidTestImplementation 'androidx.test:runner:1.6.1' + androidTestImplementation 'androidx.test:rules:1.6.1' // Assertions - androidTestImplementation 'androidx.test.ext:junit:1.0.0' - androidTestImplementation 'androidx.test.ext:truth:1.0.0' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.ext:truth:1.6.0' androidTestImplementation 'com.google.truth:truth:1.1.3' // Espresso dependencies - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0' - androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.6.1' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.6.1' + androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.6.1' // The following Espresso dependency can be either "implementation" // or "androidTestImplementation", depending on whether you want the // dependency to appear on your APK's compile classpath or the test APK // classpath. - androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.6.1' } diff --git a/packages/espresso/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/espresso/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/espresso/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/espresso/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/espresso/example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java b/packages/espresso/example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java deleted file mode 100644 index d9c1188ab84..00000000000 --- a/packages/espresso/example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java +++ /dev/null @@ -1,27 +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. - -// Generated file. -// -// If you wish to remove Flutter's multidex support, delete this entire file. -// -// Modifications to this file should be done in a copy under a different name -// as this file may be regenerated. - -package io.flutter.app; - -import android.app.Application; -import android.content.Context; -import androidx.annotation.CallSuper; -import androidx.multidex.MultiDex; - -/** Extension of {@link android.app.Application}, adding multidex support. */ -public class FlutterMultiDexApplication extends Application { - @Override - @CallSuper - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - MultiDex.install(this); - } -} diff --git a/packages/espresso/example/android/build.gradle b/packages/espresso/example/android/build.gradle index d1a176866bf..3b4e5d087a4 100644 --- a/packages/espresso/example/android/build.gradle +++ b/packages/espresso/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/espresso/example/android/settings.gradle b/packages/espresso/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/espresso/example/android/settings.gradle +++ b/packages/espresso/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/espresso/example/pubspec.yaml b/packages/espresso/example/pubspec.yaml index 8518f111365..1cafae5d3e1 100644 --- a/packages/espresso/example/pubspec.yaml +++ b/packages/espresso/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the espresso plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml index b8fc32e5459..428d2092a27 100644 --- a/packages/espresso/pubspec.yaml +++ b/packages/espresso/pubspec.yaml @@ -3,11 +3,11 @@ description: Java classes for testing Flutter apps using Espresso. Allows driving Flutter widgets from a native Espresso test. repository: https://github.com/flutter/packages/tree/main/packages/espresso issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+espresso%22 -version: 0.3.0+8 +version: 0.3.1 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md b/packages/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md index 796cb4e57cc..4e959fec76b 100644 --- a/packages/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md +++ b/packages/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.0.12 diff --git a/packages/extension_google_sign_in_as_googleapis_auth/example/android/.gitignore b/packages/extension_google_sign_in_as_googleapis_auth/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/extension_google_sign_in_as_googleapis_auth/example/android/.gitignore +++ b/packages/extension_google_sign_in_as_googleapis_auth/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle b/packages/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle index bc0712b2a23..adcf2570446 100644 --- a/packages/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle +++ b/packages/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle @@ -12,7 +12,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle b/packages/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle +++ b/packages/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml b/packages/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml index 454486b4727..36b33867f64 100644 --- a/packages/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml +++ b/packages/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Example of Google Sign-In plugin and googleapis. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: extension_google_sign_in_as_googleapis_auth: diff --git a/packages/extension_google_sign_in_as_googleapis_auth/pubspec.yaml b/packages/extension_google_sign_in_as_googleapis_auth/pubspec.yaml index f8dfe51d0ac..2903d635e79 100644 --- a/packages/extension_google_sign_in_as_googleapis_auth/pubspec.yaml +++ b/packages/extension_google_sign_in_as_googleapis_auth/pubspec.yaml @@ -11,8 +11,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.0.12 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 32358365bd7..09449fe67fd 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 1.0.3 diff --git a/packages/file_selector/file_selector/README.md b/packages/file_selector/file_selector/README.md index 798c9e9c7bc..b316a468713 100644 --- a/packages/file_selector/file_selector/README.md +++ b/packages/file_selector/file_selector/README.md @@ -10,9 +10,7 @@ A Flutter plugin that manages files and interactions with file dialogs. |-------------|---------|---------|-------|--------|-----|-------------| | **Support** | SDK 19+ | iOS 12+ | Any | 10.14+ | Any | Windows 10+ | -## Usage - -To use this plugin, add `file_selector` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/). +## Setup ### macOS diff --git a/packages/file_selector/file_selector/example/android/.gitignore b/packages/file_selector/file_selector/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/file_selector/file_selector/example/android/.gitignore +++ b/packages/file_selector/file_selector/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/file_selector/file_selector/example/android/build.gradle b/packages/file_selector/file_selector/example/android/build.gradle index 39a10a52cbe..fdf447f8a7e 100644 --- a/packages/file_selector/file_selector/example/android/build.gradle +++ b/packages/file_selector/file_selector/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/file_selector/file_selector/example/android/settings.gradle b/packages/file_selector/file_selector/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/file_selector/file_selector/example/android/settings.gradle +++ b/packages/file_selector/file_selector/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index 9d729f1b381..0fa95e0baf5 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -5,8 +5,8 @@ publish_to: none version: 1.0.0+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: file_selector: diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index d53b7b3efed..c1c105c6a52 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -6,8 +6,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.3 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index d71daf951fb..f7827e7bf56 100644 --- a/packages/file_selector/file_selector_android/CHANGELOG.md +++ b/packages/file_selector/file_selector_android/CHANGELOG.md @@ -1,7 +1,21 @@ ## NEXT +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. + +## 0.5.1+2 + +* Bumps androidx.annotation:annotation from 1.7.1 to 1.8.0. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 0.5.1+1 + +* Updates `LICENSE` file to cover licensed code used in `FileUtils.java`. + +## 0.5.1 + * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. * Updates compileSdk version to 34. +* Modifies `getDirectoryPath`, `openFile`, and `openFiles` to return file/directory paths instead of URIs. ## 0.5.0+7 diff --git a/packages/file_selector/file_selector_android/LICENSE b/packages/file_selector/file_selector_android/LICENSE index c6823b81eb8..1401a3ed50c 100644 --- a/packages/file_selector/file_selector_android/LICENSE +++ b/packages/file_selector/file_selector_android/LICENSE @@ -23,3 +23,208 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +aFileChooser + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2011 - 2013 Paul Burke + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + \ No newline at end of file diff --git a/packages/file_selector/file_selector_android/README.md b/packages/file_selector/file_selector_android/README.md index 0a24663376d..c52b5c2a860 100644 --- a/packages/file_selector/file_selector_android/README.md +++ b/packages/file_selector/file_selector_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/file_selector -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/file_selector/file_selector_android/android/build.gradle b/packages/file_selector/file_selector_android/android/build.gradle index 98a0a63deca..247f4b0c02a 100644 --- a/packages/file_selector/file_selector_android/android/build.gradle +++ b/packages/file_selector/file_selector_android/android/build.gradle @@ -38,10 +38,11 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.7.1' + implementation 'androidx.annotation:annotation:1.8.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.1.0' testImplementation 'androidx.test:core:1.3.0' + testImplementation "org.robolectric:robolectric:4.12.1" // org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. // See: https://youtrack.jetbrains.com/issue/KT-55297/kotlin-stdlib-should-declare-constraints-on-kotlin-stdlib-jdk8-and-kotlin-stdlib-jdk7 diff --git a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java index 32502ed2693..a20ab00e58e 100644 --- a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java +++ b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java @@ -4,6 +4,7 @@ package dev.flutter.packages.file_selector_android; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipData; import android.content.ContentResolver; @@ -106,6 +107,12 @@ public void openFile( public void onResult(int resultCode, @Nullable Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { final Uri uri = data.getData(); + if (uri == null) { + // No data retrieved from opening file. + result.error(new Exception("Failed to retrieve data from opening file.")); + return; + } + final GeneratedFileSelectorApi.FileResponse file = toFileResponse(uri); if (file != null) { result.success(file); @@ -183,6 +190,7 @@ public void onResult(int resultCode, @Nullable Intent data) { } @Override + @TargetApi(21) public void getDirectoryPath( @Nullable String initialDirectory, @NonNull GeneratedFileSelectorApi.Result result) { if (!sdkChecker.sdkIsAtLeast(android.os.Build.VERSION_CODES.LOLLIPOP)) { @@ -204,7 +212,22 @@ public void getDirectoryPath( public void onResult(int resultCode, @Nullable Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { final Uri uri = data.getData(); - result.success(uri.toString()); + if (uri == null) { + // No data retrieved from opening directory. + result.error(new Exception("Failed to retrieve data from opening directory.")); + return; + } + + final Uri docUri = + DocumentsContract.buildDocumentUriUsingTree( + uri, DocumentsContract.getTreeDocumentId(uri)); + try { + final String path = + FileUtils.getPathFromUri(activityPluginBinding.getActivity(), docUri); + result.success(path); + } catch (UnsupportedOperationException exception) { + result.error(exception); + } } else { result.success(null); } @@ -332,10 +355,13 @@ GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) { return null; } + final String uriPath = + FileUtils.getPathFromCopyOfFileFromUri(activityPluginBinding.getActivity(), uri); + return new GeneratedFileSelectorApi.FileResponse.Builder() .setName(name) .setBytes(bytes) - .setPath(uri.toString()) + .setPath(uriPath) .setMimeType(contentResolver.getType(uri)) .setSize(size.longValue()) .build(); diff --git a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileUtils.java b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileUtils.java new file mode 100644 index 00000000000..5d0b61312b3 --- /dev/null +++ b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileUtils.java @@ -0,0 +1,209 @@ +// 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. + +/* + * Copyright (C) 2007-2008 OpenIntents.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by the Flutter authors from the following original file: + * https://raw.githubusercontent.com/iPaulPro/aFileChooser/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java + */ + +package dev.flutter.packages.file_selector_android; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.webkit.MimeTypeMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; + +public class FileUtils { + + /** URI authority that represents access to external storage providers. */ + public static final String EXTERNAL_DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; + + /** + * Retrieves path of directory represented by the specified {@code Uri}. + * + *

Intended to handle any cases needed to return paths from URIs retrieved from open + * documents/directories by starting one of {@code Intent.ACTION_OPEN_FILE}, {@code + * Intent.ACTION_OPEN_FILES}, or {@code Intent.ACTION_OPEN_DOCUMENT_TREE}. + * + *

Will return the path for on-device directories, but does not handle external storage + * volumes. + */ + @NonNull + public static String getPathFromUri(@NonNull Context context, @NonNull Uri uri) { + String uriAuthority = uri.getAuthority(); + + if (EXTERNAL_DOCUMENT_AUTHORITY.equals(uriAuthority)) { + String uriDocumentId = DocumentsContract.getDocumentId(uri); + String[] uriDocumentIdSplit = uriDocumentId.split(":"); + + if (uriDocumentIdSplit.length < 2) { + // We expect the URI document ID to contain its storage volume and name to determine its path. + throw new UnsupportedOperationException( + "Retrieving the path of a document with an unknown storage volume or name is unsupported by this plugin."); + } + + String documentStorageVolume = uriDocumentIdSplit[0]; + + // Non-primary storage volumes come from SD cards, USB drives, etc. and are + // not handled here. + // + // Constant for primary storage volumes found at + // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/provider/DocumentsContract.java;l=255?q=Documentscont&ss=android%2Fplatform%2Fsuperproject%2Fmain. + if (!documentStorageVolume.equals("primary")) { + throw new UnsupportedOperationException( + "Retrieving the path of a document from storage volume " + + documentStorageVolume + + " is unsupported by this plugin."); + } + String innermostDirectoryName = uriDocumentIdSplit[1]; + String externalStorageDirectory = Environment.getExternalStorageDirectory().getPath(); + + return externalStorageDirectory + "/" + innermostDirectoryName; + } else { + throw new UnsupportedOperationException( + "Retrieving the path from URIs with authority " + + uriAuthority.toString() + + " is unsupported by this plugin."); + } + } + + /** + * Copies the file from the given content URI to a temporary directory, retaining the original + * file name if possible. + * + *

Each file is placed in its own directory to avoid conflicts according to the following + * scheme: {cacheDir}/{randomUuid}/{fileName} + * + *

File extension is changed to match MIME type of the file, if known. Otherwise, the extension + * is left unchanged. + * + *

If the original file name is unknown, a predefined "file_selector" filename is used and the + * file extension is deduced from the mime type. + * + *

Will return null if copying the URI contents into a new file does not complete successfully + * or if a security exception is encountered when opening the input stream to start the copying. + */ + @Nullable + public static String getPathFromCopyOfFileFromUri(@NonNull Context context, @NonNull Uri uri) { + try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { + String uuid = UUID.nameUUIDFromBytes(uri.toString().getBytes()).toString(); + File targetDirectory = new File(context.getCacheDir(), uuid); + targetDirectory.mkdir(); + targetDirectory.deleteOnExit(); + String fileName = getFileName(context, uri); + String extension = getFileExtension(context, uri); + + if (fileName == null) { + if (extension == null) { + throw new IllegalArgumentException("No name nor extension found for file."); + } else { + fileName = "file_selector" + extension; + } + } else if (extension != null) { + fileName = getBaseName(fileName) + extension; + } + + File file = new File(targetDirectory, fileName); + + try (OutputStream outputStream = new FileOutputStream(file)) { + copy(inputStream, outputStream); + return file.getPath(); + } + } catch (IOException e) { + // If closing the output stream fails, we cannot be sure that the + // target file was written in full. Flushing the stream merely moves + // the bytes into the OS, not necessarily to the file. + return null; + } catch (SecurityException e) { + // Calling `ContentResolver#openInputStream()` has been reported to throw a + // `SecurityException` on some devices in certain circumstances. Instead of crashing, we + // return `null`. + // + // See https://github.com/flutter/flutter/issues/100025 for more details. + return null; + } + } + + /** Returns the extension of file with dot, or null if it's empty. */ + private static String getFileExtension(Context context, Uri uriFile) { + String extension; + + if (uriFile.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + final MimeTypeMap mime = MimeTypeMap.getSingleton(); + extension = mime.getExtensionFromMimeType(context.getContentResolver().getType(uriFile)); + } else { + try { + Uri uriFromFile = Uri.fromFile(new File(uriFile.getPath())); + extension = MimeTypeMap.getFileExtensionFromUrl(uriFromFile.toString()); + } catch (NullPointerException e) { + // File created from uriFile was null. + return null; + } + } + + if (extension == null || extension.isEmpty()) { + return null; + } + + return "." + extension; + } + + /** Returns the name of the file provided by ContentResolver; this may be null. */ + private static String getFileName(Context context, Uri uriFile) { + try (Cursor cursor = queryFileName(context, uriFile)) { + if (cursor == null || !cursor.moveToFirst() || cursor.getColumnCount() < 1) return null; + return cursor.getString(0); + } + } + + private static Cursor queryFileName(Context context, Uri uriFile) { + return context + .getContentResolver() + .query(uriFile, new String[] {MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); + } + + private static void copy(InputStream in, OutputStream out) throws IOException { + final byte[] buffer = new byte[4 * 1024]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + out.flush(); + } + + private static String getBaseName(String fileName) { + int lastDotIndex = fileName.lastIndexOf('.'); + if (lastDotIndex < 0) { + return fileName; + } + // Basename is everything before the last '.'. + return fileName.substring(0, lastDotIndex); + } +} diff --git a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java index 63dad80954a..dd4f74e0a28 100644 --- a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java +++ b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java @@ -6,17 +6,21 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; import android.content.ClipData; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; +import android.provider.DocumentsContract; import android.provider.OpenableColumns; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -30,8 +34,10 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; public class FileSelectorAndroidPluginTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @@ -69,151 +75,193 @@ private void mockContentResolver( @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void openFileReturnsSuccessfully() throws FileNotFoundException { - final ContentResolver mockContentResolver = mock(ContentResolver.class); - - final Uri mockUri = mock(Uri.class); - when(mockUri.toString()).thenReturn("some/path/"); - mockContentResolver(mockContentResolver, mockUri, "filename", 30, "text/plain"); - - when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT)).thenReturn(mockIntent); - when(mockObjectFactory.newDataInputStream(any())).thenReturn(mock(DataInputStream.class)); - when(mockActivity.getContentResolver()).thenReturn(mockContentResolver); - when(mockActivityBinding.getActivity()).thenReturn(mockActivity); - final FileSelectorApiImpl fileSelectorApi = - new FileSelectorApiImpl( - mockActivityBinding, mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - - final GeneratedFileSelectorApi.Result mockResult = mock(GeneratedFileSelectorApi.Result.class); - fileSelectorApi.openFile( - null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); - verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); - - verify(mockActivity).startActivityForResult(mockIntent, 221); - - final ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockActivityBinding).addActivityResultListener(listenerArgumentCaptor.capture()); - - final Intent resultMockIntent = mock(Intent.class); - when(resultMockIntent.getData()).thenReturn(mockUri); - listenerArgumentCaptor.getValue().onActivityResult(221, Activity.RESULT_OK, resultMockIntent); - - final ArgumentCaptor fileCaptor = - ArgumentCaptor.forClass(GeneratedFileSelectorApi.FileResponse.class); - verify(mockResult).success(fileCaptor.capture()); - - final GeneratedFileSelectorApi.FileResponse file = fileCaptor.getValue(); - assertEquals(file.getBytes().length, 30); - assertEquals(file.getMimeType(), "text/plain"); - assertEquals(file.getName(), "filename"); - assertEquals(file.getSize(), (Long) 30L); - assertEquals(file.getPath(), "some/path/"); + try (MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { + final ContentResolver mockContentResolver = mock(ContentResolver.class); + + final Uri mockUri = mock(Uri.class); + final String mockUriPath = "/some/path"; + mockedFileUtils + .when(() -> FileUtils.getPathFromCopyOfFileFromUri(any(Context.class), eq(mockUri))) + .thenAnswer((Answer) invocation -> mockUriPath); + mockContentResolver(mockContentResolver, mockUri, "filename", 30, "text/plain"); + + when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT)).thenReturn(mockIntent); + when(mockObjectFactory.newDataInputStream(any())).thenReturn(mock(DataInputStream.class)); + when(mockActivity.getContentResolver()).thenReturn(mockContentResolver); + when(mockActivityBinding.getActivity()).thenReturn(mockActivity); + final FileSelectorApiImpl fileSelectorApi = + new FileSelectorApiImpl( + mockActivityBinding, + mockObjectFactory, + (version) -> Build.VERSION.SDK_INT >= version); + + final GeneratedFileSelectorApi.Result mockResult = + mock(GeneratedFileSelectorApi.Result.class); + fileSelectorApi.openFile( + null, + new GeneratedFileSelectorApi.FileTypes.Builder() + .setMimeTypes(Collections.emptyList()) + .setExtensions(Collections.emptyList()) + .build(), + mockResult); + verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); + + verify(mockActivity).startActivityForResult(mockIntent, 221); + + final ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); + verify(mockActivityBinding).addActivityResultListener(listenerArgumentCaptor.capture()); + + final Intent resultMockIntent = mock(Intent.class); + when(resultMockIntent.getData()).thenReturn(mockUri); + listenerArgumentCaptor.getValue().onActivityResult(221, Activity.RESULT_OK, resultMockIntent); + + final ArgumentCaptor fileCaptor = + ArgumentCaptor.forClass(GeneratedFileSelectorApi.FileResponse.class); + verify(mockResult).success(fileCaptor.capture()); + + final GeneratedFileSelectorApi.FileResponse file = fileCaptor.getValue(); + assertEquals(file.getBytes().length, 30); + assertEquals(file.getMimeType(), "text/plain"); + assertEquals(file.getName(), "filename"); + assertEquals(file.getSize(), (Long) 30L); + assertEquals(file.getPath(), mockUriPath); + } } @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void openFilesReturnsSuccessfully() throws FileNotFoundException { - final ContentResolver mockContentResolver = mock(ContentResolver.class); - - final Uri mockUri = mock(Uri.class); - when(mockUri.toString()).thenReturn("some/path/"); - mockContentResolver(mockContentResolver, mockUri, "filename", 30, "text/plain"); - - final Uri mockUri2 = mock(Uri.class); - when(mockUri2.toString()).thenReturn("some/other/path/"); - mockContentResolver(mockContentResolver, mockUri2, "filename2", 40, "image/jpg"); - - when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT)).thenReturn(mockIntent); - when(mockObjectFactory.newDataInputStream(any())).thenReturn(mock(DataInputStream.class)); - when(mockActivity.getContentResolver()).thenReturn(mockContentResolver); - when(mockActivityBinding.getActivity()).thenReturn(mockActivity); - final FileSelectorApiImpl fileSelectorApi = - new FileSelectorApiImpl( - mockActivityBinding, mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - - final GeneratedFileSelectorApi.Result mockResult = mock(GeneratedFileSelectorApi.Result.class); - fileSelectorApi.openFiles( - null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); - verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); - verify(mockIntent).putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - - verify(mockActivity).startActivityForResult(mockIntent, 222); - - final ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockActivityBinding).addActivityResultListener(listenerArgumentCaptor.capture()); - - final Intent resultMockIntent = mock(Intent.class); - final ClipData mockClipData = mock(ClipData.class); - when(mockClipData.getItemCount()).thenReturn(2); - - final ClipData.Item mockClipDataItem = mock(ClipData.Item.class); - when(mockClipDataItem.getUri()).thenReturn(mockUri); - when(mockClipData.getItemAt(0)).thenReturn(mockClipDataItem); - - final ClipData.Item mockClipDataItem2 = mock(ClipData.Item.class); - when(mockClipDataItem2.getUri()).thenReturn(mockUri2); - when(mockClipData.getItemAt(1)).thenReturn(mockClipDataItem2); - - when(resultMockIntent.getClipData()).thenReturn(mockClipData); - - listenerArgumentCaptor.getValue().onActivityResult(222, Activity.RESULT_OK, resultMockIntent); - - final ArgumentCaptor fileListCaptor = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(fileListCaptor.capture()); - - final List fileList = fileListCaptor.getValue(); - assertEquals(fileList.get(0).getBytes().length, 30); - assertEquals(fileList.get(0).getMimeType(), "text/plain"); - assertEquals(fileList.get(0).getName(), "filename"); - assertEquals(fileList.get(0).getSize(), (Long) 30L); - assertEquals(fileList.get(0).getPath(), "some/path/"); - - assertEquals(fileList.get(1).getBytes().length, 40); - assertEquals(fileList.get(1).getMimeType(), "image/jpg"); - assertEquals(fileList.get(1).getName(), "filename2"); - assertEquals(fileList.get(1).getSize(), (Long) 40L); - assertEquals(fileList.get(1).getPath(), "some/other/path/"); + try (MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { + + final ContentResolver mockContentResolver = mock(ContentResolver.class); + + final Uri mockUri = mock(Uri.class); + final String mockUriPath = "some/path/"; + mockedFileUtils + .when(() -> FileUtils.getPathFromCopyOfFileFromUri(any(Context.class), eq(mockUri))) + .thenAnswer((Answer) invocation -> mockUriPath); + mockContentResolver(mockContentResolver, mockUri, "filename", 30, "text/plain"); + + final Uri mockUri2 = mock(Uri.class); + final String mockUri2Path = "some/other/path/"; + mockedFileUtils + .when(() -> FileUtils.getPathFromCopyOfFileFromUri(any(Context.class), eq(mockUri2))) + .thenAnswer((Answer) invocation -> mockUri2Path); + mockContentResolver(mockContentResolver, mockUri2, "filename2", 40, "image/jpg"); + + when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT)).thenReturn(mockIntent); + when(mockObjectFactory.newDataInputStream(any())).thenReturn(mock(DataInputStream.class)); + when(mockActivity.getContentResolver()).thenReturn(mockContentResolver); + when(mockActivityBinding.getActivity()).thenReturn(mockActivity); + final FileSelectorApiImpl fileSelectorApi = + new FileSelectorApiImpl( + mockActivityBinding, + mockObjectFactory, + (version) -> Build.VERSION.SDK_INT >= version); + + final GeneratedFileSelectorApi.Result mockResult = + mock(GeneratedFileSelectorApi.Result.class); + fileSelectorApi.openFiles( + null, + new GeneratedFileSelectorApi.FileTypes.Builder() + .setMimeTypes(Collections.emptyList()) + .setExtensions(Collections.emptyList()) + .build(), + mockResult); + verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); + verify(mockIntent).putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + + verify(mockActivity).startActivityForResult(mockIntent, 222); + + final ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); + verify(mockActivityBinding).addActivityResultListener(listenerArgumentCaptor.capture()); + + final Intent resultMockIntent = mock(Intent.class); + final ClipData mockClipData = mock(ClipData.class); + when(mockClipData.getItemCount()).thenReturn(2); + + final ClipData.Item mockClipDataItem = mock(ClipData.Item.class); + when(mockClipDataItem.getUri()).thenReturn(mockUri); + when(mockClipData.getItemAt(0)).thenReturn(mockClipDataItem); + + final ClipData.Item mockClipDataItem2 = mock(ClipData.Item.class); + when(mockClipDataItem2.getUri()).thenReturn(mockUri2); + when(mockClipData.getItemAt(1)).thenReturn(mockClipDataItem2); + + when(resultMockIntent.getClipData()).thenReturn(mockClipData); + + listenerArgumentCaptor.getValue().onActivityResult(222, Activity.RESULT_OK, resultMockIntent); + + final ArgumentCaptor fileListCaptor = ArgumentCaptor.forClass(List.class); + verify(mockResult).success(fileListCaptor.capture()); + + final List fileList = fileListCaptor.getValue(); + assertEquals(fileList.get(0).getBytes().length, 30); + assertEquals(fileList.get(0).getMimeType(), "text/plain"); + assertEquals(fileList.get(0).getName(), "filename"); + assertEquals(fileList.get(0).getSize(), (Long) 30L); + assertEquals(fileList.get(0).getPath(), mockUriPath); + + assertEquals(fileList.get(1).getBytes().length, 40); + assertEquals(fileList.get(1).getMimeType(), "image/jpg"); + assertEquals(fileList.get(1).getName(), "filename2"); + assertEquals(fileList.get(1).getSize(), (Long) 40L); + assertEquals(fileList.get(1).getPath(), mockUri2Path); + } } @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void getDirectoryPathReturnsSuccessfully() { - final Uri mockUri = mock(Uri.class); - when(mockUri.toString()).thenReturn("some/path/"); - - when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE)).thenReturn(mockIntent); - when(mockActivityBinding.getActivity()).thenReturn(mockActivity); - final FileSelectorApiImpl fileSelectorApi = - new FileSelectorApiImpl( - mockActivityBinding, - mockObjectFactory, - (version) -> Build.VERSION_CODES.LOLLIPOP >= version); - - final GeneratedFileSelectorApi.Result mockResult = mock(GeneratedFileSelectorApi.Result.class); - fileSelectorApi.getDirectoryPath(null, mockResult); - - verify(mockActivity).startActivityForResult(mockIntent, 223); - - final ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockActivityBinding).addActivityResultListener(listenerArgumentCaptor.capture()); - - final Intent resultMockIntent = mock(Intent.class); - when(resultMockIntent.getData()).thenReturn(mockUri); - listenerArgumentCaptor.getValue().onActivityResult(223, Activity.RESULT_OK, resultMockIntent); - - verify(mockResult).success("some/path/"); + try (MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { + final Uri mockUri = mock(Uri.class); + final String mockUriPath = "some/path/"; + final String mockUriId = "someId"; + final Uri mockUriUsingTree = mock(Uri.class); + + mockedFileUtils + .when(() -> FileUtils.getPathFromUri(any(Context.class), eq(mockUriUsingTree))) + .thenAnswer((Answer) invocation -> mockUriPath); + + try (MockedStatic mockedDocumentsContract = + mockStatic(DocumentsContract.class)) { + + mockedDocumentsContract + .when(() -> DocumentsContract.getTreeDocumentId(mockUri)) + .thenAnswer((Answer) invocation -> mockUriId); + mockedDocumentsContract + .when(() -> DocumentsContract.buildDocumentUriUsingTree(mockUri, mockUriId)) + .thenAnswer((Answer) invocation -> mockUriUsingTree); + + when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE)).thenReturn(mockIntent); + when(mockActivityBinding.getActivity()).thenReturn(mockActivity); + final FileSelectorApiImpl fileSelectorApi = + new FileSelectorApiImpl( + mockActivityBinding, + mockObjectFactory, + (version) -> Build.VERSION_CODES.LOLLIPOP >= version); + + final GeneratedFileSelectorApi.Result mockResult = + mock(GeneratedFileSelectorApi.Result.class); + fileSelectorApi.getDirectoryPath(null, mockResult); + + verify(mockActivity).startActivityForResult(mockIntent, 223); + + final ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); + verify(mockActivityBinding).addActivityResultListener(listenerArgumentCaptor.capture()); + + final Intent resultMockIntent = mock(Intent.class); + when(resultMockIntent.getData()).thenReturn(mockUri); + listenerArgumentCaptor + .getValue() + .onActivityResult(223, Activity.RESULT_OK, resultMockIntent); + + verify(mockResult).success(mockUriPath); + } + } } @Test diff --git a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileUtilsTest.java b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileUtilsTest.java new file mode 100644 index 00000000000..760874317ef --- /dev/null +++ b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileUtilsTest.java @@ -0,0 +1,255 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.file_selector_android; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.webkit.MimeTypeMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.stubbing.Answer; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.ShadowMimeTypeMap; + +@RunWith(RobolectricTestRunner.class) +public class FileUtilsTest { + + private Context context; + ShadowContentResolver shadowContentResolver; + ContentResolver contentResolver; + + @Before + public void before() { + context = ApplicationProvider.getApplicationContext(); + contentResolver = spy(context.getContentResolver()); + shadowContentResolver = shadowOf(context.getContentResolver()); + ShadowMimeTypeMap mimeTypeMap = shadowOf(MimeTypeMap.getSingleton()); + mimeTypeMap.addExtensionMimeTypeMapping("txt", "document/txt"); + mimeTypeMap.addExtensionMimeTypeMapping("jpg", "image/jpeg"); + mimeTypeMap.addExtensionMimeTypeMapping("png", "image/png"); + mimeTypeMap.addExtensionMimeTypeMapping("webp", "image/webp"); + } + + @Test + public void getPathFromUri_returnsExpectedPathForExternalDocumentUri() { + // Uri that represents Documents/test directory on device: + Uri uri = + Uri.parse( + "content://com.android.externalstorage.documents/tree/primary%3ADocuments%2Ftest"); + try (MockedStatic mockedDocumentsContract = + mockStatic(DocumentsContract.class)) { + mockedDocumentsContract + .when(() -> DocumentsContract.getDocumentId(uri)) + .thenAnswer((Answer) invocation -> "primary:Documents/test"); + String path = FileUtils.getPathFromUri(context, uri); + String externalStorageDirectoryPath = Environment.getExternalStorageDirectory().getPath(); + String expectedPath = externalStorageDirectoryPath + "/Documents/test"; + assertEquals(path, expectedPath); + } + } + + @Test + public void getPathFromUri_throwExceptionForExternalDocumentUriWithNonPrimaryStorageVolume() { + // Uri that represents Documents/test directory from some external storage volume ("external" for this test): + Uri uri = + Uri.parse( + "content://com.android.externalstorage.documents/tree/external%3ADocuments%2Ftest"); + try (MockedStatic mockedDocumentsContract = + mockStatic(DocumentsContract.class)) { + mockedDocumentsContract + .when(() -> DocumentsContract.getDocumentId(uri)) + .thenAnswer((Answer) invocation -> "external:Documents/test"); + assertThrows( + UnsupportedOperationException.class, () -> FileUtils.getPathFromUri(context, uri)); + } + } + + @Test + public void getPathFromUri_throwExceptionForUriWithUnhandledAuthority() { + Uri uri = Uri.parse("content://com.unsupported.authority/tree/primary%3ADocuments%2Ftest"); + assertThrows(UnsupportedOperationException.class, () -> FileUtils.getPathFromUri(context, uri)); + } + + @Test + public void getPathFromCopyOfFileFromUri_returnsPathWithContent() throws IOException { + Uri uri = MockContentProvider.PNG_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("fileStream".getBytes(UTF_8))); + + String path = FileUtils.getPathFromCopyOfFileFromUri(context, uri); + File file = new File(path); + int size = (int) file.length(); + byte[] bytes = new byte[size]; + + BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file)); + buf.read(bytes, 0, bytes.length); + buf.close(); + + assertTrue(bytes.length > 0); + String fileStream = new String(bytes, UTF_8); + assertEquals("fileStream", fileStream); + } + + @Test + public void getPathFromCopyOfFileFromUri_returnsNullPathWhenSecurityExceptionThrown() + throws IOException { + Uri uri = Uri.parse("content://dummy/dummy.png"); + + ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContentResolver.openInputStream(any(Uri.class))).thenThrow(SecurityException.class); + + Context mockContext = mock(Context.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + + String path = FileUtils.getPathFromCopyOfFileFromUri(mockContext, uri); + + assertNull(path); + } + + @Test + public void getFileExtension_returnsExpectedFileExtension() throws IOException { + Uri uri = MockContentProvider.TXT_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("fileStream".getBytes(UTF_8))); + + String path = FileUtils.getPathFromCopyOfFileFromUri(context, uri); + System.out.println(path); + assertTrue(path.endsWith(".txt")); + } + + @Test + public void getFileName_returnsExpectedName() throws IOException { + Uri uri = MockContentProvider.PNG_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("fileStream".getBytes(UTF_8))); + String path = FileUtils.getPathFromCopyOfFileFromUri(context, uri); + assertTrue(path.endsWith("a.b.png")); + } + + @Test + public void getPathFromCopyOfFileFromUri_returnsExpectedPathForUriWithNoExtensionInBaseName() + throws IOException { + Uri uri = MockContentProvider.NO_EXTENSION_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("fileStream".getBytes(UTF_8))); + String path = FileUtils.getPathFromCopyOfFileFromUri(context, uri); + assertTrue(path.endsWith("abc.png")); + } + + @Test + public void getPathFromCopyOfFileFromUri_returnsExpectedPathForUriWithMismatchedTypeToFile() + throws IOException { + Uri uri = MockContentProvider.WEBP_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("fileStream".getBytes(UTF_8))); + String path = FileUtils.getPathFromCopyOfFileFromUri(context, uri); + assertTrue(path.endsWith("c.d.webp")); + } + + @Test + public void getPathFromCopyOfFileFromUri_returnsExpectedPathForUriWithUnknownType() + throws IOException { + Uri uri = MockContentProvider.UNKNOWN_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("fileStream".getBytes(UTF_8))); + String path = FileUtils.getPathFromCopyOfFileFromUri(context, uri); + assertTrue(path.endsWith("e.f.g")); + } + + private static class MockContentProvider extends ContentProvider { + public static final Uri TXT_URI = Uri.parse("content://dummy/dummydocument"); + public static final Uri PNG_URI = Uri.parse("content://dummy/a.b.png"); + public static final Uri WEBP_URI = Uri.parse("content://dummy/c.d.png"); + public static final Uri UNKNOWN_URI = Uri.parse("content://dummy/e.f.g"); + public static final Uri NO_EXTENSION_URI = Uri.parse("content://dummy/abc"); + + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public Cursor query( + @NonNull Uri uri, + @Nullable String[] projection, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); + cursor.addRow(new Object[] {uri.getLastPathSegment()}); + return cursor; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + if (uri.equals(TXT_URI)) return "document/txt"; + if (uri.equals(PNG_URI)) return "image/png"; + if (uri.equals(WEBP_URI)) return "image/webp"; + if (uri.equals(NO_EXTENSION_URI)) return "image/png"; + return null; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete( + @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update( + @NonNull Uri uri, + @Nullable ContentValues values, + @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } + } +} diff --git a/packages/file_selector/file_selector_android/example/android/app/src/androidTest/java/dev/flutter/packages/file_selector_android_example/FileSelectorAndroidTest.java b/packages/file_selector/file_selector_android/example/android/app/src/androidTest/java/dev/flutter/packages/file_selector_android_example/FileSelectorAndroidTest.java index b464d10c249..b7f2390d019 100644 --- a/packages/file_selector/file_selector_android/example/android/app/src/androidTest/java/dev/flutter/packages/file_selector_android_example/FileSelectorAndroidTest.java +++ b/packages/file_selector/file_selector_android/example/android/app/src/androidTest/java/dev/flutter/packages/file_selector_android_example/FileSelectorAndroidTest.java @@ -14,15 +14,20 @@ import static androidx.test.espresso.intent.Intents.intending; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra; +import static org.junit.Assert.assertEquals; import android.app.Activity; import android.app.Instrumentation; import android.content.ClipData; import android.content.Intent; import android.net.Uri; +import android.view.View; import androidx.test.core.app.ActivityScenario; +import androidx.test.espresso.flutter.api.WidgetAssertion; +import androidx.test.espresso.flutter.model.WidgetInfo; import androidx.test.espresso.intent.rule.IntentsRule; import androidx.test.ext.junit.rules.ActivityScenarioRule; +import java.util.UUID; import org.junit.Rule; import org.junit.Test; @@ -50,17 +55,41 @@ public void perform(DriverExtensionActivity activity) { public void openImageFile() { clearAnySystemDialog(); + final String fileName = "dummy.png"; final Instrumentation.ActivityResult result = new Instrumentation.ActivityResult( Activity.RESULT_OK, - new Intent().setData(Uri.parse("content://file_selector_android_test/dummy.png"))); + new Intent().setData(Uri.parse("content://file_selector_android_test/" + fileName))); intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(result); onFlutterWidget(withText("Open an image")).perform(click()); onFlutterWidget(withText("Press to open an image file(png, jpg)")).perform(click()); intended(hasAction(Intent.ACTION_OPEN_DOCUMENT)); onFlutterWidget(withValueKey("result_image_name")) - .check(matches(withText("content://file_selector_android_test/dummy.png"))); + .check( + new WidgetAssertion() { + @Override + public void check(View flutterView, WidgetInfo widgetInfo) { + String filePath = widgetInfo.getText(); + String expectedContentUri = "content://file_selector_android_test/dummy.png"; + String expectedContentUriUuid = + UUID.nameUUIDFromBytes(expectedContentUri.toString().getBytes()).toString(); + + myActivityTestRule + .getScenario() + .onActivity( + activity -> { + String expectedCacheDirectory = activity.getCacheDir().getPath(); + String expectedFilePath = + expectedCacheDirectory + + "/" + + expectedContentUriUuid + + "/" + + fileName; + assertEquals(filePath, expectedFilePath); + }); + } + }); } @Test diff --git a/packages/file_selector/file_selector_android/example/android/app/src/main/java/io/flutter/plugins/DartIntegrationTest.java b/packages/file_selector/file_selector_android/example/android/app/src/main/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/file_selector/file_selector_android/example/android/app/src/main/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/file_selector/file_selector_android/example/android/app/src/main/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/file_selector/file_selector_android/example/android/build.gradle b/packages/file_selector/file_selector_android/example/android/build.gradle index e36f0fd3705..6e01a69f49a 100644 --- a/packages/file_selector/file_selector_android/example/android/build.gradle +++ b/packages/file_selector/file_selector_android/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/file_selector/file_selector_android/example/android/settings.gradle b/packages/file_selector/file_selector_android/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/file_selector/file_selector_android/example/android/settings.gradle +++ b/packages/file_selector/file_selector_android/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/file_selector/file_selector_android/example/pubspec.yaml b/packages/file_selector/file_selector_android/example/pubspec.yaml index 96261cf2f32..d9ad24cfc44 100644 --- a/packages/file_selector/file_selector_android/example/pubspec.yaml +++ b/packages/file_selector/file_selector_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the file_selector_android plugin. publish_to: 'none' environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: file_selector_android: diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index 9f0843771ff..db12e199880 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -2,11 +2,11 @@ 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+7 +version: 0.5.1+2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 50615cbce70..5ee388e7a03 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.5.3 + +* Converts implementation to Swift. +* Re-adds Swift Package Manager compatibility. + +## 0.5.2+1 + +* Temporarily remove Swift Package Manager compatibility to resolve issues with Cocoapods builds. + +## 0.5.2 + +* Adds Swift Package Manager compatibility. + ## 0.5.1+9 * Adjusts implementation for testabiity. diff --git a/packages/file_selector/file_selector_ios/README.md b/packages/file_selector/file_selector_ios/README.md index 104d05c6e47..8acef650373 100644 --- a/packages/file_selector/file_selector_ios/README.md +++ b/packages/file_selector/file_selector_ios/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/file_selector -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.pbxproj index f6d350beac6..cca3c71f4be 100644 --- a/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,13 +9,13 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 21160A929DC757957DE39F1E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 000792269CB6B9FE88AC567C /* Pods_Runner.framework */; }; + 337EF9CE2BF7945F0079FB1A /* FileSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 337EF9CD2BF7945F0079FB1A /* FileSelectorTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 6165A2F80DFA224EAF50A1D5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC3841659BF3693FAC5A2F8F /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - C71AE4C8281C6B6B0086307A /* FileSelectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C71AE4C5281C6B530086307A /* FileSelectorTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,6 +45,7 @@ 000792269CB6B9FE88AC567C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 337EF9CD2BF7945F0079FB1A /* FileSelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSelectorTests.swift; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4A27CC0DB4EF6669B637A1E8 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 5667547C6832727A744371E2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; @@ -63,7 +64,6 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AC3841659BF3693FAC5A2F8F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C71AE4B6281C6A090086307A /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - C71AE4C5281C6B530086307A /* FileSelectorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileSelectorTests.m; sourceTree = ""; }; F818CE2D7CDF8AFF94707327 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -150,7 +150,7 @@ C71AE4C4281C6B370086307A /* RunnerTests */ = { isa = PBXGroup; children = ( - C71AE4C5281C6B530086307A /* FileSelectorTests.m */, + 337EF9CD2BF7945F0079FB1A /* FileSelectorTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -223,6 +223,7 @@ }; C71AE4B5281C6A090086307A = { CreatedOnToolsVersion = 13.1; + LastSwiftMigration = 1510; TestTargetID = 97C146ED1CF9000F007C117D; }; }; @@ -376,7 +377,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C71AE4C8281C6B6B0086307A /* FileSelectorTests.m in Sources */, + 337EF9CE2BF7945F0079FB1A /* FileSelectorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -636,6 +637,7 @@ baseConfigurationReference = 4A27CC0DB4EF6669B637A1E8 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; @@ -647,6 +649,8 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.FileSelectorTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; @@ -659,6 +663,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -678,6 +683,7 @@ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.FileSelectorTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; @@ -691,6 +697,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -710,6 +717,7 @@ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.FileSelectorTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; diff --git a/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 828f48e73f8..5b6df8a1c38 100644 --- a/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + -@property(nonatomic) UIViewController *presentedController; -@end - -@implementation TestPresenter -- (void)presentViewController:(UIViewController *)viewControllerToPresent - animated:(BOOL)animated - completion:(void (^__nullable)(void))completion { - self.presentedController = viewControllerToPresent; -} -@end - -#pragma mark - - -@interface FileSelectorTests : XCTestCase - -@end - -@implementation FileSelectorTests - -- (void)testPickerPresents { - FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; - UIDocumentPickerViewController *picker = - [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] - inMode:UIDocumentPickerModeImport]; - TestPresenter *presenter = [[TestPresenter alloc] init]; - plugin.documentPickerViewControllerOverride = picker; - plugin.viewPresenterOverride = presenter; - - [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:NO] - completion:^(NSArray *paths, FlutterError *error){ - }]; - - XCTAssertEqualObjects(picker.delegate, plugin); - XCTAssertEqualObjects(presenter.presentedController, picker); -} - -- (void)testReturnsPickedFiles { - FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; - XCTestExpectation *completionWasCalled = [self expectationWithDescription:@"completion"]; - UIDocumentPickerViewController *picker = - [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] - inMode:UIDocumentPickerModeImport]; - plugin.documentPickerViewControllerOverride = picker; - [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] - allowMultiSelection:YES] - completion:^(NSArray *paths, FlutterError *error) { - NSArray *expectedPaths = @[ @"/file1.txt", @"/file2.txt" ]; - XCTAssertEqualObjects(paths, expectedPaths); - [completionWasCalled fulfill]; - }]; - [plugin documentPicker:picker - didPickDocumentsAtURLs:@[ - [NSURL URLWithString:@"file:///file1.txt"], [NSURL URLWithString:@"file:///file2.txt"] - ]]; - [self waitForExpectationsWithTimeout:1.0 handler:nil]; -} - -- (void)testCancellingPickerReturnsNil { - FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; - UIDocumentPickerViewController *picker = - [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] - inMode:UIDocumentPickerModeImport]; - plugin.documentPickerViewControllerOverride = picker; - - XCTestExpectation *completionWasCalled = [self expectationWithDescription:@"completion"]; - [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:NO] - completion:^(NSArray *paths, FlutterError *error) { - XCTAssertEqual(paths.count, 0); - [completionWasCalled fulfill]; - }]; - [plugin documentPickerWasCancelled:picker]; - [self waitForExpectationsWithTimeout:1.0 handler:nil]; -} - -@end diff --git a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift new file mode 100644 index 00000000000..0466b808ed1 --- /dev/null +++ b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift @@ -0,0 +1,85 @@ +// 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 XCTest + +@testable import file_selector_ios + +final class TestViewPresenter: ViewPresenter { + public var presentedController: UIViewController? + + func present( + _ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)? = nil + ) { + presentedController = viewControllerToPresent + } +} + +class FileSelectorTests: XCTestCase { + func testPickerPresents() throws { + let plugin = FileSelectorPlugin() + let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) + let presenter = TestViewPresenter() + plugin.documentPickerViewControllerOverride = picker + plugin.viewPresenterOverride = presenter + + plugin.openFile( + config: FileSelectorConfig(utis: [], allowMultiSelection: false) + ) { _ in } + + XCTAssertEqual(plugin.pendingCompletions.count, 1) + XCTAssertTrue(picker.delegate === plugin.pendingCompletions.first) + XCTAssertTrue(presenter.presentedController === picker) + } + + func testReturnsPickedFiles() throws { + let plugin = FileSelectorPlugin() + let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) + plugin.documentPickerViewControllerOverride = picker + plugin.viewPresenterOverride = TestViewPresenter() + let completionWasCalled = expectation(description: "completion") + + plugin.openFile( + config: FileSelectorConfig(utis: [], allowMultiSelection: false) + ) { result in + switch result { + case .success(let paths): + XCTAssertEqual(paths, ["/file1.txt", "/file2.txt"]) + case .failure(let error): + XCTFail("\(error)") + } + completionWasCalled.fulfill() + } + plugin.pendingCompletions.first!.documentPicker( + picker, + didPickDocumentsAt: [URL(string: "file:///file1.txt")!, URL(string: "file:///file2.txt")!]) + + waitForExpectations(timeout: 30.0) + XCTAssertTrue(plugin.pendingCompletions.isEmpty) + } + + func testCancellingPickerReturnsEmptyList() throws { + let plugin = FileSelectorPlugin() + let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) + plugin.documentPickerViewControllerOverride = picker + plugin.viewPresenterOverride = TestViewPresenter() + let completionWasCalled = expectation(description: "completion") + + plugin.openFile( + config: FileSelectorConfig(utis: [], allowMultiSelection: false) + ) { result in + switch result { + case .success(let paths): + XCTAssertEqual(paths.count, 0) + case .failure(let error): + XCTFail("\(error)") + } + completionWasCalled.fulfill() + } + plugin.pendingCompletions.first!.documentPickerWasCancelled(picker) + + waitForExpectations(timeout: 30.0) + XCTAssertTrue(plugin.pendingCompletions.isEmpty) + } +} diff --git a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m b/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m deleted file mode 100644 index e17469c636b..00000000000 --- a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m +++ /dev/null @@ -1,106 +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 "FFSFileSelectorPlugin.h" -#import "FFSFileSelectorPlugin_Test.h" -#import "messages.g.h" - -#import - -// TODO(stuartmorgan): When migrating to Swift, eliminate this in favor of -// adding FFSViewPresenter conformance to UIViewController. -@interface FFSPresentingViewController : NSObject -- (instancetype)initWithViewController:(nullable UIViewController *)controller; -// The wrapped controller. -@property(nonatomic) UIViewController *controller; -@end - -@implementation FFSPresentingViewController -- (instancetype)initWithViewController:(nullable UIViewController *)controller { - self = [super init]; - if (self) { - _controller = controller; - } - return self; -} - -- (void)presentViewController:(UIViewController *)viewControllerToPresent - animated:(BOOL)animated - completion:(void (^__nullable)(void))completion { - [self.controller presentViewController:viewControllerToPresent - animated:animated - completion:completion]; -} -@end - -#pragma mark - - -@implementation FFSFileSelectorPlugin - -#pragma mark - FFSFileSelectorApi - -- (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config - completion:(void (^)(NSArray *_Nullable, - FlutterError *_Nullable))completion { - UIDocumentPickerViewController *documentPicker = - self.documentPickerViewControllerOverride - ?: [[UIDocumentPickerViewController alloc] - initWithDocumentTypes:config.utis - inMode:UIDocumentPickerModeImport]; - documentPicker.delegate = self; - documentPicker.allowsMultipleSelection = config.allowMultiSelection; - - id presenter = - self.viewPresenterOverride - ?: [[FFSPresentingViewController alloc] - initWithViewController:UIApplication.sharedApplication.delegate.window - .rootViewController]; - if (presenter) { - objc_setAssociatedObject(documentPicker, @selector(openFileSelectorWithConfig:completion:), - completion, OBJC_ASSOCIATION_COPY_NONATOMIC); - [presenter presentViewController:documentPicker animated:YES completion:nil]; - } else { - completion(nil, [FlutterError errorWithCode:@"error" - message:@"Missing root view controller." - details:nil]); - } -} - -#pragma mark - FlutterPlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; - SetUpFFSFileSelectorApi(registrar.messenger, plugin); -} - -#pragma mark - UIDocumentPickerDelegate - -- (void)documentPicker:(UIDocumentPickerViewController *)controller - didPickDocumentsAtURLs:(NSArray *)urls { - NSMutableArray *paths = [NSMutableArray arrayWithCapacity:urls.count]; - for (NSURL *url in urls) { - [paths addObject:url.path]; - }; - [self sendBackResults:paths error:nil forPicker:controller]; -} - -- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { - [self sendBackResults:@[] error:nil forPicker:controller]; -} - -#pragma mark - Helper Methods - -- (void)sendBackResults:(NSArray *)results - error:(FlutterError *)error - forPicker:(UIDocumentPickerViewController *)picker { - void (^completionBlock)(NSArray *, FlutterError *) = - objc_getAssociatedObject(picker, @selector(openFileSelectorWithConfig:completion:)); - if (completionBlock) { - completionBlock(results, error); - objc_setAssociatedObject(picker, @selector(openFileSelectorWithConfig:completion:), nil, - OBJC_ASSOCIATION_ASSIGN); - } -} - -@end diff --git a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h b/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h deleted file mode 100644 index a5e2040fc63..00000000000 --- a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h +++ /dev/null @@ -1,29 +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 "FFSFileSelectorPlugin.h" - -@import UIKit; - -#import "messages.g.h" - -/// Interface for presenting a view controller, to allow injecting an alternate -/// test implementation. -@protocol FFSViewPresenter -/// Wrapper for -[UIViewController presentViewController:animated:completion:]. -- (void)presentViewController:(UIViewController *_Nonnull)viewControllerToPresent - animated:(BOOL)animated - completion:(void (^__nullable)(void))completion; -@end - -// This header is available in the Test module. Import via "@import file_selector_ios.Test;". -@interface FFSFileSelectorPlugin () - -/// Overrides the view controller used for presenting the document picker. -@property(nonatomic) id _Nullable viewPresenterOverride; - -/// Overrides the UIDocumentPickerViewController used for file picking. -@property(nonatomic) UIDocumentPickerViewController *_Nullable documentPickerViewControllerOverride; - -@end diff --git a/packages/file_selector/file_selector_ios/ios/Classes/FileSelectorPlugin.modulemap b/packages/file_selector/file_selector_ios/ios/Classes/FileSelectorPlugin.modulemap deleted file mode 100644 index 4ff40260ffb..00000000000 --- a/packages/file_selector/file_selector_ios/ios/Classes/FileSelectorPlugin.modulemap +++ /dev/null @@ -1,10 +0,0 @@ -framework module file_selector_ios { - umbrella header "file_selector_ios-umbrella.h" - - export * - module * { export * } - - explicit module Test { - header "FFSFileSelectorPlugin_Test.h" - } -} diff --git a/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h b/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h deleted file mode 100644 index d79d3642b3e..00000000000 --- a/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h +++ /dev/null @@ -1,6 +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 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 deleted file mode 100644 index d0f9d977f4b..00000000000 --- a/packages/file_selector/file_selector_ios/ios/Classes/messages.g.h +++ /dev/null @@ -1,39 +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 (v13.0.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -#import - -@protocol FlutterBinaryMessenger; -@protocol FlutterMessageCodec; -@class FlutterError; -@class FlutterStandardTypedData; - -NS_ASSUME_NONNULL_BEGIN - -@class FFSFileSelectorConfig; - -@interface FFSFileSelectorConfig : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithUtis:(NSArray *)utis - allowMultiSelection:(BOOL)allowMultiSelection; -@property(nonatomic, copy) NSArray *utis; -@property(nonatomic, assign) BOOL allowMultiSelection; -@end - -/// The codec used by FFSFileSelectorApi. -NSObject *FFSFileSelectorApiGetCodec(void); - -@protocol FFSFileSelectorApi -- (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config - completion:(void (^)(NSArray *_Nullable, - FlutterError *_Nullable))completion; -@end - -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 deleted file mode 100644 index 1905261653b..00000000000 --- a/packages/file_selector/file_selector_ios/ios/Classes/messages.g.m +++ /dev/null @@ -1,136 +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 (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 - -static NSArray *wrapResult(id result, FlutterError *error) { - if (error) { - return @[ - error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] - ]; - } - return @[ result ?: [NSNull null] ]; -} -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { - id result = array[key]; - return (result == [NSNull null]) ? nil : result; -} - -@interface FFSFileSelectorConfig () -+ (FFSFileSelectorConfig *)fromList:(NSArray *)list; -+ (nullable FFSFileSelectorConfig *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@implementation FFSFileSelectorConfig -+ (instancetype)makeWithUtis:(NSArray *)utis - allowMultiSelection:(BOOL)allowMultiSelection { - FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init]; - pigeonResult.utis = utis; - pigeonResult.allowMultiSelection = allowMultiSelection; - return pigeonResult; -} -+ (FFSFileSelectorConfig *)fromList:(NSArray *)list { - FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init]; - pigeonResult.utis = GetNullableObjectAtIndex(list, 0); - pigeonResult.allowMultiSelection = [GetNullableObjectAtIndex(list, 1) boolValue]; - return pigeonResult; -} -+ (nullable FFSFileSelectorConfig *)nullableFromList:(NSArray *)list { - return (list) ? [FFSFileSelectorConfig fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.utis ?: [NSNull null], - @(self.allowMultiSelection), - ]; -} -@end - -@interface FFSFileSelectorApiCodecReader : FlutterStandardReader -@end -@implementation FFSFileSelectorApiCodecReader -- (nullable id)readValueOfType:(UInt8)type { - switch (type) { - case 128: - return [FFSFileSelectorConfig fromList:[self readValue]]; - default: - return [super readValueOfType:type]; - } -} -@end - -@interface FFSFileSelectorApiCodecWriter : FlutterStandardWriter -@end -@implementation FFSFileSelectorApiCodecWriter -- (void)writeValue:(id)value { - if ([value isKindOfClass:[FFSFileSelectorConfig class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else { - [super writeValue:value]; - } -} -@end - -@interface FFSFileSelectorApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation FFSFileSelectorApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FFSFileSelectorApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FFSFileSelectorApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *FFSFileSelectorApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - static dispatch_once_t sPred = 0; - dispatch_once(&sPred, ^{ - FFSFileSelectorApiCodecReaderWriter *readerWriter = - [[FFSFileSelectorApiCodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - -void SetUpFFSFileSelectorApi(id binaryMessenger, - NSObject *api) { - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile" - binaryMessenger:binaryMessenger - codec:FFSFileSelectorApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(openFileSelectorWithConfig:completion:)], - @"FFSFileSelectorApi api (%@) doesn't respond to " - @"@selector(openFileSelectorWithConfig:completion:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FFSFileSelectorConfig *arg_config = GetNullableObjectAtIndex(args, 0); - [api openFileSelectorWithConfig:arg_config - completion:^(NSArray *_Nullable output, - FlutterError *_Nullable error) { - callback(wrapResult(output, error)); - }]; - }]; - } else { - [channel setMessageHandler:nil]; - } - } -} diff --git a/packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec b/packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec index 79156132418..6d09c9b3105 100644 --- a/packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec +++ b/packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec @@ -13,11 +13,14 @@ Displays the native iOS document picker. s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios' } - s.source_files = 'Classes/**/*.{h,m}' - s.module_map = 'Classes/FileSelectorPlugin.modulemap' + s.source_files = 'file_selector_ios/Sources/file_selector_ios/**/*.swift' s.dependency 'Flutter' s.platform = :ios, '12.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' - s.resource_bundles = {'file_selector_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } + s.resource_bundles = {'file_selector_ios_privacy' => ['file_selector_ios/Sources/file_selector_ios/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/file_selector/file_selector_ios/ios/file_selector_ios/Package.swift b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Package.swift new file mode 100644 index 00000000000..0b32b081347 --- /dev/null +++ b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "file_selector_ios", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "file-selector-ios", targets: ["file_selector_ios"]) + ], + dependencies: [], + targets: [ + .target( + name: "file_selector_ios", + dependencies: [], + resources: [ + .process("Resources") + ], + cSettings: [ + .headerSearchPath("include/file_selector_ios") + ] + ) + ] +) diff --git a/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/FileSelectorPlugin.swift b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/FileSelectorPlugin.swift new file mode 100644 index 00000000000..f1f72cc2e28 --- /dev/null +++ b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/FileSelectorPlugin.swift @@ -0,0 +1,80 @@ +// 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 ObjectiveC +import UIKit + +/// Bridge between a UIDocumentPickerViewController and its Pigeon callback. +class PickerCompletionBridge: NSObject, UIDocumentPickerDelegate { + let completion: (Result<[String], Error>) -> Void + /// The plugin instance that owns this object, to ensure that it lives as long as the picker it + /// serves as a delegate for. Instances are responsible for removing themselves from their owner + /// on completion. + let owner: FileSelectorPlugin + + init(completion: @escaping (Result<[String], Error>) -> Void, owner: FileSelectorPlugin) { + self.completion = completion + self.owner = owner + } + + func documentPicker( + _ controller: UIDocumentPickerViewController, + didPickDocumentsAt urls: [URL] + ) { + sendResult(urls.map({ $0.path })) + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + sendResult([]) + } + + private func sendResult(_ result: [String]) { + completion(.success(result)) + owner.pendingCompletions.remove(self) + } +} + +public class FileSelectorPlugin: NSObject, FlutterPlugin, FileSelectorApi { + /// Owning references to pending completion callbacks. + /// + /// This is necessary since the objects need to live until a UIDocumentPickerDelegate method is + /// called on the delegate, but the delegate is weak. Objects in this set are responsible for + /// removing themselves from it. + var pendingCompletions: Set = [] + /// Overridden document picker, for testing. + var documentPickerViewControllerOverride: UIDocumentPickerViewController? + /// Overridden view presenter, for testing. + var viewPresenterOverride: ViewPresenter? + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = FileSelectorPlugin() + FileSelectorApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance) + } + + func openFile(config: FileSelectorConfig, completion: @escaping (Result<[String], Error>) -> Void) + { + let completionBridge = PickerCompletionBridge(completion: completion, owner: self) + let documentPicker = + documentPickerViewControllerOverride + ?? UIDocumentPickerViewController( + // See comment in messages.dart for why this is safe. + documentTypes: config.utis as! [String], + in: .import) + documentPicker.allowsMultipleSelection = config.allowMultiSelection + documentPicker.delegate = completionBridge + + let presenter = + self.viewPresenterOverride ?? UIApplication.shared.delegate?.window??.rootViewController + if let presenter = presenter { + pendingCompletions.insert(completionBridge) + presenter.present(documentPicker, animated: true, completion: nil) + } else { + completion( + .failure(PigeonError(code: "error", message: "Missing root view controller.", details: nil)) + ) + } + } + +} diff --git a/packages/file_selector/file_selector_ios/ios/Resources/PrivacyInfo.xcprivacy b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/file_selector/file_selector_ios/ios/Resources/PrivacyInfo.xcprivacy rename to packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/Resources/PrivacyInfo.xcprivacy diff --git a/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/ViewPresenter.swift b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/ViewPresenter.swift new file mode 100644 index 00000000000..4a702bdbef4 --- /dev/null +++ b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/ViewPresenter.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. + +import UIKit + +/// Protocol for UIViewController methods relating to presenting a controller. +/// +/// This protocol exists to allow injecting an alternate implementation for testing. +protocol ViewPresenter { + /// Presents a view controller modally. + func present( + _ viewControllerToPresent: UIViewController, + animated flag: Bool, + completion: (() -> Void)? + ) +} + +/// ViewPresenter is intentionally a direct passthroguh to UIViewController. +extension UIViewController: ViewPresenter {} diff --git a/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/messages.g.swift b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/messages.g.swift new file mode 100644 index 00000000000..43f1bffeac8 --- /dev/null +++ b/packages/file_selector/file_selector_ios/ios/file_selector_ios/Sources/file_selector_ios/messages.g.swift @@ -0,0 +1,164 @@ +// 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 (v19.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +/// Generated class from Pigeon that represents data sent in messages. +struct FileSelectorConfig { + var utis: [String?] + var allowMultiSelection: Bool + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> FileSelectorConfig? { + let utis = __pigeon_list[0] as! [String?] + let allowMultiSelection = __pigeon_list[1] as! Bool + + return FileSelectorConfig( + utis: utis, + allowMultiSelection: allowMultiSelection + ) + } + func toList() -> [Any?] { + return [ + utis, + allowMultiSelection, + ] + } +} + +private class FileSelectorApiCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return FileSelectorConfig.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class FileSelectorApiCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? FileSelectorConfig { + super.writeByte(128) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class FileSelectorApiCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return FileSelectorApiCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return FileSelectorApiCodecWriter(data: data) + } +} + +class FileSelectorApiCodec: FlutterStandardMessageCodec { + static let shared = FileSelectorApiCodec(readerWriter: FileSelectorApiCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol FileSelectorApi { + func openFile(config: FileSelectorConfig, completion: @escaping (Result<[String], Error>) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class FileSelectorApiSetup { + /// The codec used by FileSelectorApi. + static var codec: FlutterStandardMessageCodec { FileSelectorApiCodec.shared } + /// Sets up an instance of `FileSelectorApi` to handle messages through the `binaryMessenger`. + static func setUp( + binaryMessenger: FlutterBinaryMessenger, api: FileSelectorApi?, + messageChannelSuffix: String = "" + ) { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let openFileChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + openFileChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let configArg = args[0] as! FileSelectorConfig + api.openFile(config: configArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + openFileChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart b/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart index 3c2e4a2b8a9..d0eb22762bc 100644 --- a/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart +++ b/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart @@ -22,8 +22,7 @@ class FileSelectorIOS extends FileSelectorPlatform { String? confirmButtonText, }) async { final List path = (await _hostApi.openFile(FileSelectorConfig( - utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups), - allowMultiSelection: false))) + utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups)))) .cast(); return path.isEmpty ? null : XFile(path.first); } 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 6ec723c8b70..e86dd9c9057 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,9 +1,9 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v13.0.0), do not edit directly. +// Autogenerated from Pigeon (v19.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 +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -11,6 +11,13 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + List wrapResponse( {Object? result, PlatformException? error, bool empty = false}) { if (empty) { @@ -24,8 +31,8 @@ List wrapResponse( class FileSelectorConfig { FileSelectorConfig({ - required this.utis, - required this.allowMultiSelection, + this.utis = const [], + this.allowMultiSelection = false, }); List utis; @@ -75,36 +82,44 @@ class FileSelectorApi { /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - FileSelectorApi({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; + FileSelectorApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec codec = _FileSelectorApiCodec(); + static const MessageCodec pigeonChannelCodec = + _FileSelectorApiCodec(); - Future> openFile(FileSelectorConfig arg_config) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile', codec, - binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_config]) as List?; - if (replyList == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyList.length > 1) { + final String __pigeon_messageChannelSuffix; + + Future> openFile(FileSelectorConfig config) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([config]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); - } else if (replyList[0] == null) { + } else if (__pigeon_replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as List?)!.cast(); + return (__pigeon_replyList[0] as List?)!.cast(); } } } diff --git a/packages/file_selector/file_selector_ios/pigeons/messages.dart b/packages/file_selector/file_selector_ios/pigeons/messages.dart index 66706cc2406..a793a2af5a9 100644 --- a/packages/file_selector/file_selector_ios/pigeons/messages.dart +++ b/packages/file_selector/file_selector_ios/pigeons/messages.dart @@ -7,16 +7,15 @@ 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', - objcOptions: ObjcOptions( - prefix: 'FFS', - ), + swiftOut: 'ios/file_selector_ios/Sources/file_selector_ios/messages.g.swift', copyrightHeader: 'pigeons/copyright.txt', )) class FileSelectorConfig { FileSelectorConfig( {this.utis = const [], this.allowMultiSelection = false}); + // TODO(stuartmorgan): Declare these as non-nullable generics once + // https://github.com/flutter/flutter/issues/97848 is fixed. In practice, + // the values will never be null, and the native implementation assumes that. List utis; bool allowMultiSelection; } diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index cae1f78b840..24c0d84231b 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+9 +version: 0.5.3 environment: sdk: ^3.2.3 @@ -14,7 +14,7 @@ flutter: platforms: ios: dartPluginClass: FileSelectorIOS - pluginClass: FFSFileSelectorPlugin + pluginClass: FileSelectorPlugin dependencies: file_selector_platform_interface: ^2.3.0 @@ -26,7 +26,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.4 - pigeon: ^13.0.0 + pigeon: ^19.0.0 topics: - files diff --git a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart index 9c065f3bd1e..41c6fe869d2 100644 --- a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart +++ b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart @@ -22,7 +22,7 @@ void main() { setUp(() { mockApi = MockTestFileSelectorApi(); - TestFileSelectorApi.setup(mockApi); + TestFileSelectorApi.setUp(mockApi); }); test('registered instance', () { 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 7b0ace73ef9..db24932f71a 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,9 +1,9 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v13.0.0), do not edit directly. +// Autogenerated from Pigeon (v19.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: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -39,23 +39,30 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { abstract class TestFileSelectorApi { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = _TestFileSelectorApiCodec(); + static const MessageCodec pigeonChannelCodec = + _TestFileSelectorApiCodec(); Future> openFile(FileSelectorConfig config); - static void setup(TestFileSelectorApi? api, - {BinaryMessenger? binaryMessenger}) { + static void setUp( + TestFileSelectorApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.file_selector_ios.FileSelectorApi.openFile was null.'); diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index 49e00d29913..938cf91c72b 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.9.2+1 diff --git a/packages/file_selector/file_selector_linux/README.md b/packages/file_selector/file_selector_linux/README.md index f3c2d30c424..aa0653238c5 100644 --- a/packages/file_selector/file_selector_linux/README.md +++ b/packages/file_selector/file_selector_linux/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/file_selector -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index 957bd132be6..1e20e859cfa 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: file_selector_linux: diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index 88a33e9aede..4470f465069 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.9.2+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index 03cc77eafe7..a48b7ff16bb 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 0.9.4 + +* Adds Swift Package Manager compatibility. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 0.9.3+3 diff --git a/packages/file_selector/file_selector_macos/README.md b/packages/file_selector/file_selector_macos/README.md index 6fcca5a34bd..ce243a811f8 100644 --- a/packages/file_selector/file_selector_macos/README.md +++ b/packages/file_selector/file_selector_macos/README.md @@ -26,5 +26,5 @@ or read/write access: depending on your use case. [1]: https://pub.dev/packages/file_selector -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://flutter.dev/desktop#entitlements-and-the-app-sandbox diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index e211b2364a5..c240f271d7b 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: file_selector_macos: diff --git a/packages/file_selector/file_selector_macos/macos/file_selector_macos.podspec b/packages/file_selector/file_selector_macos/macos/file_selector_macos.podspec index fb460b41c97..bb4bffee9e0 100644 --- a/packages/file_selector/file_selector_macos/macos/file_selector_macos.podspec +++ b/packages/file_selector/file_selector_macos/macos/file_selector_macos.podspec @@ -12,7 +12,7 @@ Displays native macOS open and save panels. s.homepage = 'https://github.com/flutter/packages/tree/main/packages/file_selector' s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_macos' } - s.source_files = 'Classes/**/*' + s.source_files = 'file_selector_macos/Sources/file_selector_macos/**/*.swift' s.dependency 'FlutterMacOS' s.platform = :osx, '10.14' diff --git a/packages/file_selector/file_selector_macos/macos/file_selector_macos/Package.swift b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Package.swift new file mode 100644 index 00000000000..97bd0cd8d5d --- /dev/null +++ b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Package.swift @@ -0,0 +1,25 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "file_selector_macos", + platforms: [ + .macOS("10.14") + ], + products: [ + .library(name: "file-selector-macos", targets: ["file_selector_macos"]) + ], + dependencies: [], + targets: [ + .target( + name: "file_selector_macos", + dependencies: [] + ) + ] +) diff --git a/packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/FileSelectorPlugin.swift similarity index 100% rename from packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift rename to packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/FileSelectorPlugin.swift diff --git a/packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift similarity index 100% rename from packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift rename to packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift diff --git a/packages/file_selector/file_selector_macos/pigeons/messages.dart b/packages/file_selector/file_selector_macos/pigeons/messages.dart index 85b2996baf8..698ebcfc100 100644 --- a/packages/file_selector/file_selector_macos/pigeons/messages.dart +++ b/packages/file_selector/file_selector_macos/pigeons/messages.dart @@ -5,7 +5,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( input: 'pigeons/messages.dart', - swiftOut: 'macos/Classes/messages.g.swift', + swiftOut: + 'macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift', dartOut: 'lib/src/messages.g.dart', dartTestOut: 'test/messages_test.g.dart', copyrightHeader: 'pigeons/copyright.txt', diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index 8f1599612c1..00d738d20a3 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -2,11 +2,11 @@ name: file_selector_macos description: macOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.3+3 +version: 0.9.4 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index 03b3ab11380..073092d3171 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.6.2 diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index 6cc78012949..d432a511644 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.6.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: cross_file: ^0.3.0 diff --git a/packages/file_selector/file_selector_web/README.md b/packages/file_selector/file_selector_web/README.md index 3906b2f5082..9c5f94a8ef4 100644 --- a/packages/file_selector/file_selector_web/README.md +++ b/packages/file_selector/file_selector_web/README.md @@ -12,7 +12,7 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/file_selector -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin ## Limitations on the Web platform diff --git a/packages/file_selector/file_selector_web/example/README.md b/packages/file_selector/file_selector_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/file_selector/file_selector_web/example/README.md +++ b/packages/file_selector/file_selector_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index a20abf6153b..7d50124560a 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.9.3+1 diff --git a/packages/file_selector/file_selector_windows/README.md b/packages/file_selector/file_selector_windows/README.md index 7c6ddff34e4..cf2b722b1a5 100644 --- a/packages/file_selector/file_selector_windows/README.md +++ b/packages/file_selector/file_selector_windows/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/file_selector -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index beec643cd93..b3c5f8123c4 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: file_selector_platform_interface: ^2.6.0 diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index f031be5fd4d..93159d888fd 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.9.3+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 065983360e9..dd4c337ee6b 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.1.11+1 + +* Allows custom animation duration for the NavigationRail and + BottomNavigationBar transitions. [flutter/flutter#112938](https://github.com/flutter/flutter/issues/112938) + +## 0.1.11 + +* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3. +* Migrates deprecated MaterialState and MaterialStateProperty to WidgetState and WidgetStateProperty. + +## 0.1.10+2 + +* Reduce rebuilds when invoking `isActive` method. + ## 0.1.10+1 * Removes a broken design document link from the README. diff --git a/packages/flutter_adaptive_scaffold/example/android/.gitignore b/packages/flutter_adaptive_scaffold/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/flutter_adaptive_scaffold/example/android/.gitignore +++ b/packages/flutter_adaptive_scaffold/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/flutter_adaptive_scaffold/example/android/build.gradle b/packages/flutter_adaptive_scaffold/example/android/build.gradle index 582d60a2faa..d13ef556e26 100644 --- a/packages/flutter_adaptive_scaffold/example/android/build.gradle +++ b/packages/flutter_adaptive_scaffold/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/flutter_adaptive_scaffold/example/android/settings.gradle b/packages/flutter_adaptive_scaffold/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/flutter_adaptive_scaffold/example/android/settings.gradle +++ b/packages/flutter_adaptive_scaffold/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/flutter_adaptive_scaffold/example/lib/main.dart b/packages/flutter_adaptive_scaffold/example/lib/main.dart index 735b25a668a..13a6418973d 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/main.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/main.dart @@ -863,14 +863,14 @@ class _EmailTile extends StatelessWidget { child: OutlinedButton( onPressed: () {}, style: ButtonStyle( - shape: MaterialStateProperty.all( + shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0)), ), - backgroundColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all( const Color.fromARGB(255, 245, 241, 248), ), - side: MaterialStateProperty.all( + side: WidgetStateProperty.all( const BorderSide( width: 0.0, color: Colors.transparent), ), @@ -885,14 +885,14 @@ class _EmailTile extends StatelessWidget { child: OutlinedButton( onPressed: () {}, style: ButtonStyle( - shape: MaterialStateProperty.all( + shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0)), ), - backgroundColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all( const Color.fromARGB(255, 245, 241, 248), ), - side: MaterialStateProperty.all( + side: WidgetStateProperty.all( const BorderSide( width: 0.0, color: Colors.transparent), ), diff --git a/packages/flutter_adaptive_scaffold/example/pubspec.yaml b/packages/flutter_adaptive_scaffold/example/pubspec.yaml index 5c327ac86c8..8f424d78ce5 100644 --- a/packages/flutter_adaptive_scaffold/example/pubspec.yaml +++ b/packages/flutter_adaptive_scaffold/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 0.0.1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart index 663817a7ec1..c2a2e68327b 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart @@ -338,8 +338,8 @@ class AdaptiveScaffold extends StatefulWidget { NavigationBarTheme.of(context); return NavigationBarTheme( data: currentNavBarTheme.copyWith( - iconTheme: MaterialStateProperty.resolveWith( - (Set states) { + iconTheme: WidgetStateProperty.resolveWith( + (Set states) { return currentNavBarTheme.iconTheme ?.resolve(states) ?.copyWith(size: iconSize) ?? diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 4fc0687152d..5a7bcfe579b 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -95,7 +95,7 @@ class WidthPlatformBreakpoint extends Breakpoint { // Null boundaries are unbounded, assign the max/min of their associated // direction on a number line. - final double width = MediaQuery.of(context).size.width; + final double width = MediaQuery.sizeOf(context).width; final double lowerBound = begin ?? double.negativeInfinity; final double upperBound = end ?? double.infinity; @@ -126,6 +126,6 @@ abstract class Breakpoint { const Breakpoint(); /// A method that returns true based on conditions related to the context of - /// the screen such as MediaQuery.of(context).size.width. + /// the screen such as MediaQuery.sizeOf(context).width. bool isActive(BuildContext context); } diff --git a/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart b/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart index ce3f5a374c4..38789fb8660 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart @@ -74,12 +74,14 @@ class SlotLayout extends StatefulWidget { WidgetBuilder? builder, Widget Function(Widget, Animation)? inAnimation, Widget Function(Widget, Animation)? outAnimation, + Duration? duration, required Key key, }) => SlotLayoutConfig._( builder: builder, inAnimation: inAnimation, outAnimation: outAnimation, + duration: duration, key: key, ); @@ -96,7 +98,7 @@ class _SlotLayoutState extends State chosenWidget = SlotLayout.pickWidget(context, widget.config); bool hasAnimation = false; return AnimatedSwitcher( - duration: const Duration(milliseconds: 1000), + duration: chosenWidget?.duration ?? const Duration(milliseconds: 1000), layoutBuilder: (Widget? currentChild, List previousChildren) { final Stack elements = Stack( children: [ @@ -137,6 +139,7 @@ class SlotLayoutConfig extends StatelessWidget { required this.builder, this.inAnimation, this.outAnimation, + this.duration, }); /// The child Widget that [SlotLayout] eventually returns with an animation. @@ -160,6 +163,9 @@ class SlotLayoutConfig extends StatelessWidget { /// as the returned widget. final Widget Function(Widget, Animation)? outAnimation; + /// The amount of time taken by the execution of the in and out animations. + final Duration? duration; + /// An empty [SlotLayoutConfig] to be placed in a slot to indicate that the slot /// should show nothing. static SlotLayoutConfig empty() { diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index cd322d41d2a..c3815b4abf3 100644 --- a/packages/flutter_adaptive_scaffold/pubspec.yaml +++ b/packages/flutter_adaptive_scaffold/pubspec.yaml @@ -1,12 +1,12 @@ name: flutter_adaptive_scaffold description: Widgets to easily build adaptive layouts, including navigation elements. -version: 0.1.10+1 +version: 0.1.11+1 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22 repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" dependencies: flutter: diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart index 5924f0a8a74..3166e5b382d 100644 --- a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart +++ b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart @@ -120,11 +120,13 @@ void main() { testWidgets( 'slot layout properly switches between items with the appropriate animation', (WidgetTester tester) async { - await tester.pumpWidget(slot(300, tester)); + await tester + .pumpWidget(slot(300, const Duration(milliseconds: 1000), tester)); expect(begin, findsOneWidget); expect(end, findsNothing); - await tester.pumpWidget(slot(500, tester)); + await tester + .pumpWidget(slot(500, const Duration(milliseconds: 1000), tester)); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); expect(tester.widget(slideOut('0')).position.value, @@ -146,7 +148,7 @@ void main() { testWidgets('AnimatedSwitcher does not spawn duplicate keys on rapid resize', (WidgetTester tester) async { // Populate the smaller slot layout and let the animation settle. - await tester.pumpWidget(slot(300, tester)); + await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester)); await tester.pumpAndSettle(); expect(begin, findsOneWidget); expect(end, findsNothing); @@ -157,12 +159,12 @@ void main() { for (int i = 0; i < 2; i++) { // Resize between the two slot layouts, but do not pump the animation // until completion. - await tester.pumpWidget(slot(500, tester)); + await tester.pumpWidget(slot(500, const Duration(seconds: 1), tester)); await tester.pump(const Duration(milliseconds: 100)); expect(begin, findsOneWidget); expect(end, findsOneWidget); - await tester.pumpWidget(slot(300, tester)); + await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester)); await tester.pump(const Duration(milliseconds: 100)); expect(begin, findsOneWidget); expect(end, findsOneWidget); @@ -171,18 +173,18 @@ void main() { testWidgets('slot layout can tolerate rapid changes in breakpoints', (WidgetTester tester) async { - await tester.pumpWidget(slot(300, tester)); + await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester)); expect(begin, findsOneWidget); expect(end, findsNothing); - await tester.pumpWidget(slot(500, tester)); + await tester.pumpWidget(slot(500, const Duration(seconds: 1), tester)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(slideOut('0')).position.value, offsetMoreOrLessEquals(const Offset(-0.1, 0), epsilon: 0.05)); expect(tester.widget(slideIn('400')).position.value, offsetMoreOrLessEquals(const Offset(-0.9, 0), epsilon: 0.05)); - await tester.pumpWidget(slot(300, tester)); + await tester.pumpWidget(slot(300, const Duration(seconds: 1), tester)); await tester.pumpAndSettle(); expect(begin, findsOneWidget); expect(end, findsNothing); @@ -243,11 +245,35 @@ void main() { tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); }); + testWidgets('adaptive layout can adjust animation duration', + (WidgetTester tester) async { + // Populate the smaller slot layout and let the animation settle. + await tester + .pumpWidget(slot(300, const Duration(milliseconds: 100), tester)); + await tester.pumpAndSettle(); + expect(begin, findsOneWidget); + expect(end, findsNothing); + + // expand in 1/5 second. + await tester + .pumpWidget(slot(500, const Duration(milliseconds: 200), tester)); + + // after 100ms, we expect both widgets to be present. + await tester.pump(const Duration(milliseconds: 50)); + expect(begin, findsOneWidget); + expect(end, findsOneWidget); + + // After 1/5 second, all animations should be done. + await tester.pump(const Duration(milliseconds: 200)); + expect(begin, findsNothing); + expect(end, findsOneWidget); + + await tester.pumpAndSettle(); + }); + testWidgets('adaptive layout does not animate when animations off', (WidgetTester tester) async { final Finder testBreakpoint = find.byKey(const Key('Test Breakpoint')); - final Finder secondaryTestBreakpoint = - find.byKey(const Key('Secondary Test Breakpoint')); await tester.pumpWidget( await layout(width: 400, tester: tester, animations: false)); @@ -257,9 +283,6 @@ void main() { expect(tester.getTopLeft(testBreakpoint), const Offset(10, 10)); expect(tester.getBottomRight(testBreakpoint), const Offset(200, 790)); - expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10)); - expect( - tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790)); }); } @@ -306,6 +329,7 @@ Future layout({ TextDirection directionality = TextDirection.ltr, double? bodyRatio, bool animations = true, + int durationMs = 1000, }) async { await tester.binding.setSurfaceSize(Size(width, 800)); return MediaQuery( @@ -415,7 +439,7 @@ AnimatedWidget leftInOut(Widget child, Animation animation) { ); } -MediaQuery slot(double width, WidgetTester tester) { +MediaQuery slot(double width, Duration duration, WidgetTester tester) { return MediaQuery( data: MediaQueryData.fromView(tester.view).copyWith(size: Size(width, 800)), child: Directionality( @@ -425,12 +449,14 @@ MediaQuery slot(double width, WidgetTester tester) { TestBreakpoint0(): SlotLayout.from( inAnimation: leftOutIn, outAnimation: leftInOut, + duration: duration, key: const Key('0'), builder: (_) => const SizedBox(width: 10, height: 10), ), TestBreakpoint400(): SlotLayout.from( inAnimation: leftOutIn, outAnimation: leftInOut, + duration: duration, key: const Key('400'), builder: (_) => const SizedBox(width: 10, height: 10), ), diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index 6ecb20a2701..c0975092db6 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_adaptive_scaffold/src/breakpoints.dart'; import 'package:flutter_test/flutter_test.dart'; import 'simulated_layout.dart'; @@ -51,4 +51,42 @@ void main() { expect(find.byKey(const Key('Breakpoints.largeDesktop')), findsOneWidget); expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); }, variant: TargetPlatformVariant.desktop()); + + testWidgets('Breakpoint.isActive should not trigger unnecessary rebuilds', + (WidgetTester tester) async { + await tester.pumpWidget(const DymmyWidget()); + expect(find.byKey(const Key('button')), findsOneWidget); + + // First build. + expect(DymmyWidget.built, isTrue); + + // Invoke `isActive` method. + await tester.tap(find.byKey(const Key('button'))); + DymmyWidget.built = false; + + // Should not rebuild after modifying any property in `MediaQuery`. + tester.platformDispatcher.textScaleFactorTestValue = 2; + await tester.pumpAndSettle(); + expect(DymmyWidget.built, isFalse); + }); +} + +class DymmyWidget extends StatelessWidget { + const DymmyWidget({super.key}); + + static bool built = false; + @override + Widget build(BuildContext context) { + built = true; + return Directionality( + textDirection: TextDirection.ltr, + child: ElevatedButton( + key: const Key('button'), + onPressed: () { + Breakpoints.small.isActive(context); + }, + child: const SizedBox(), + ), + ); + } } diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 3f3e5560846..c2553510b65 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 4.1.11 diff --git a/packages/flutter_image/example/android/.gitignore b/packages/flutter_image/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/flutter_image/example/android/.gitignore +++ b/packages/flutter_image/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/flutter_image/example/android/build.gradle b/packages/flutter_image/example/android/build.gradle index 39a10a52cbe..fdf447f8a7e 100644 --- a/packages/flutter_image/example/android/build.gradle +++ b/packages/flutter_image/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/flutter_image/example/android/settings.gradle b/packages/flutter_image/example/android/settings.gradle index 82a32952732..f246a74091b 100644 --- a/packages/flutter_image/example/android/settings.gradle +++ b/packages/flutter_image/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { @@ -21,4 +21,4 @@ buildscript { classpath "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" } } -apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" \ No newline at end of file +apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" diff --git a/packages/flutter_image/example/macos/Runner/DebugProfile.entitlements b/packages/flutter_image/example/macos/Runner/DebugProfile.entitlements index 08c3ab17cc2..d8e18ed9e74 100644 --- a/packages/flutter_image/example/macos/Runner/DebugProfile.entitlements +++ b/packages/flutter_image/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/flutter_image/example/macos/Runner/Release.entitlements b/packages/flutter_image/example/macos/Runner/Release.entitlements index 3618034fd14..51d323ec35a 100644 --- a/packages/flutter_image/example/macos/Runner/Release.entitlements +++ b/packages/flutter_image/example/macos/Runner/Release.entitlements @@ -5,6 +5,7 @@ com.apple.security.network.client com.apple.security.app-sandbox - + + diff --git a/packages/flutter_image/example/pubspec.yaml b/packages/flutter_image/example/pubspec.yaml index 3ee96174bd5..cf877461305 100644 --- a/packages/flutter_image/example/pubspec.yaml +++ b/packages/flutter_image/example/pubspec.yaml @@ -6,8 +6,8 @@ publish_to: "none" version: 1.0.0+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: cupertino_icons: ^1.0.2 diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 87731b2afd0..3507a03e6f2 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -6,8 +6,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 4.1.11 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/flutter_lints/CHANGELOG.md b/packages/flutter_lints/CHANGELOG.md index 5e5343cc446..f3cfed6ed68 100644 --- a/packages/flutter_lints/CHANGELOG.md +++ b/packages/flutter_lints/CHANGELOG.md @@ -1,3 +1,15 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 4.0.0 + +* Updates `package:lints` dependency to version 4.0.0, with the following changes: + * adds `library_annotations` + * adds `no_wildcard_variable_uses` + * removes `package_prefixed_library_names` + * removes `library_names` + ## 3.0.2 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/flutter_lints/example/pubspec.yaml b/packages/flutter_lints/example/pubspec.yaml index b03c52c0acc..f6575cc56e0 100644 --- a/packages/flutter_lints/example/pubspec.yaml +++ b/packages/flutter_lints/example/pubspec.yaml @@ -4,7 +4,7 @@ description: A project that showcases how to enable the recommended lints for Fl publish_to: none environment: - sdk: ^3.1.0 + sdk: ^3.2.0 # Add the latest version of `package:flutter_lints` as a dev_dependency. The # lint set provided by this package is activated in the `analysis_options.yaml` diff --git a/packages/flutter_lints/pubspec.yaml b/packages/flutter_lints/pubspec.yaml index 051e5cf2a09..b1a931bec31 100644 --- a/packages/flutter_lints/pubspec.yaml +++ b/packages/flutter_lints/pubspec.yaml @@ -2,13 +2,13 @@ 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.2 +version: 4.0.0 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: - lints: ^3.0.0 + lints: ^4.0.0 # Code is not allowed in this package. Do not add any dependencies or dev_dependencies. topics: diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index 36ff215e015..2b42efadc5f 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.7.3 + +* Adds horizontal scrolling for table when using `tableColumnWidth: FixedColumnWidth(width)`. + +## 0.7.2+1 + +* Fixes a crash caused by text selection when `selectable` is true and `onSelectionChanged` is null. + +## 0.7.2 + +* Multiple code blocks within a single markdown will now use separate ScrollControllers. + +## 0.7.1 + +* Allows for choosing a custom font feature to create superscript in footnotes when the font does not support the `supr` font feature. + * Use the `superscriptFontFeatureTag` property in `MarkdownStyleSheet`. + * For example, for the `Roboto` font which doesn't support `supr`, you can set `numr`. + ## 0.7.0 * **BREAKING CHANGES**: @@ -150,198 +168,198 @@ ## 0.6.10 - * Update `markdown` dependency +* Update `markdown` dependency ## 0.6.9+1 - * Remove build status badge from `README.md` +* Remove build status badge from `README.md` ## 0.6.9 - * Leading spaces in a paragraph and in list items are now ignored according to [GFM #192](https://github.github.com/gfm/#example-192) and [GFM #236](https://github.github.com/gfm/#example-236). +* Leading spaces in a paragraph and in list items are now ignored according to [GFM #192](https://github.github.com/gfm/#example-192) and [GFM #236](https://github.github.com/gfm/#example-236). ## 0.6.8 - * Added option paddingBuilders +* Added option paddingBuilders ## 0.6.7 - * Fix `unnecessary_import` lint errors. - * Added option pPadding - * Added options h1Padding - h6Padding +* Fix `unnecessary_import` lint errors. +* Added option pPadding +* Added options h1Padding - h6Padding ## 0.6.6 - * Soft line break +* Soft line break ## 0.6.5 - * Fix unique Keys for RichText blocks +* Fix unique Keys for RichText blocks ## 0.6.4 - * Fix merging of spans when first span is not a TextSpan +* Fix merging of spans when first span is not a TextSpan ## 0.6.3 - * Fixed `onTap`, now the changed hyperlinks are reflected even with keeping the same link name unchanged. +* Fixed `onTap`, now the changed hyperlinks are reflected even with keeping the same link name unchanged. ## 0.6.2 - * Updated metadata for new source location - * Style changes to conform to flutter/packages analyzer settings +* Updated metadata for new source location +* Style changes to conform to flutter/packages analyzer settings - ## 0.6.1 +## 0.6.1 - * Added builder option bulletBuilder +* Added builder option bulletBuilder ## 0.6.0 - * Null safety release - * Added stylesheet option listBulletPadding - * Fixed blockquote inline styling - * Added onTapText handler for selectable text +* Null safety release +* Added stylesheet option listBulletPadding +* Fixed blockquote inline styling +* Added onTapText handler for selectable text ## 0.6.0-nullsafety.2 - * Dependencies updated for null safety +* Dependencies updated for null safety ## 0.6.0-nullsafety.1 - * Fix null safety on web - * Image test mocks fixed for null safety +* Fix null safety on web +* Image test mocks fixed for null safety ## 0.6.0-nullsafety.0 - * Initial null safety migration. +* Initial null safety migration. ## 0.5.2 - * Added `MarkdownListItemCrossAxisAlignment` to allow for intrinsic height +* Added `MarkdownListItemCrossAxisAlignment` to allow for intrinsic height measurements of lists. ## 0.5.1 - * Fix user defined builders +* Fix user defined builders ## 0.5.0 - * BREAKING CHANGE: `MarkdownTapLinkCallback` now has three parameters, not one, exposing more +* BREAKING CHANGE: `MarkdownTapLinkCallback` now has three parameters, not one, exposing more information about a tapped link. - * Note for upgraders, the old single parameter `href` is now the second parameter to match the specification. - * Android example upgraded - * Test coverage updated to match GitHub Flavoured Markdown and CommonMark - * Handle links with empty descriptions - * Handle empty rows in tables + * Note for upgraders, the old single parameter `href` is now the second parameter to match the specification. +* Android example upgraded +* Test coverage updated to match GitHub Flavoured Markdown and CommonMark +* Handle links with empty descriptions +* Handle empty rows in tables ## 0.4.4 - * Fix handling of newline character in blockquote - * Add new example demo - * Use the start attribute in ordered list to set the first number - * Revert changes made in PR #235 (which broke newline handling) +* Fix handling of newline character in blockquote +* Add new example demo +* Use the start attribute in ordered list to set the first number +* Revert changes made in PR #235 (which broke newline handling) ## 0.4.3 - * Fix merging of `MarkdownStyleSheets` - * Fix `MarkdownStyleSheet` textScaleFactor to use default value of 1.0, if not provided, instead using the textScaleFactor of the nearest MediaQuery +* Fix merging of `MarkdownStyleSheets` +* Fix `MarkdownStyleSheet` textScaleFactor to use default value of 1.0, if not provided, instead using the textScaleFactor of the nearest MediaQuery ## 0.4.2 - * Fix parsing of image caption & alt attributes - * Fix baseline alignment in lists - * Support `LineBreakSyntax` +* Fix parsing of image caption & alt attributes +* Fix baseline alignment in lists +* Support `LineBreakSyntax` ## 0.4.1 - * Downgrade Flutter minimum from 1.17.1 to 1.17.0 for Pub +* Downgrade Flutter minimum from 1.17.1 to 1.17.0 for Pub ## 0.4.0 - * Updated for Flutter 1.17 - * Ignore newlines in paragraphs - * Improve handling of horizontal rules +* Updated for Flutter 1.17 +* Ignore newlines in paragraphs +* Improve handling of horizontal rules ## 0.3.5 - * Fix hardcoded colors and improve Darktheme - * Fix text alignment when formatting is involved +* Fix hardcoded colors and improve Darktheme +* Fix text alignment when formatting is involved ## 0.3.4 - * Add support for text paragraphs and blockquotes. +* Add support for text paragraphs and blockquotes. ## 0.3.3 - * Add the ability to control the scroll position of the `MarkdownWidget`. +* Add the ability to control the scroll position of the `MarkdownWidget`. ## 0.3.2 - * Uplift `package:markdown` dependency version to enable deleting HTML unescape URI workaround - * Explictly state that Flutter 1.10.7 is the minimum supported Flutter version in the library `pubspec.yaml`. +* Uplift `package:markdown` dependency version to enable deleting HTML unescape URI workaround +* Explictly state that Flutter 1.10.7 is the minimum supported Flutter version in the library `pubspec.yaml`. ## 0.3.1 - * Expose `tableColumnWidth` - * Add `MarkdownStyleSheet.fromCupertinoTheme` - * Fix `MarkdownStyleSheet.blockquote` - * Flutter for web support - * Add physic and shrinkWrap to Markdown widget - * Add MarkdownBody.fitContent - * Support select text to copy - * Fix list bullet alignment - * HTML unescape URIs (temporary workaround for [dart-lang/markdown #272](https://github.com/dart-lang/markdown/issues/272)) - * Rebuilt `example/android` and `example/ios` directories +* Expose `tableColumnWidth` +* Add `MarkdownStyleSheet.fromCupertinoTheme` +* Fix `MarkdownStyleSheet.blockquote` +* Flutter for web support +* Add physic and shrinkWrap to Markdown widget +* Add MarkdownBody.fitContent +* Support select text to copy +* Fix list bullet alignment +* HTML unescape URIs (temporary workaround for [dart-lang/markdown #272](https://github.com/dart-lang/markdown/issues/272)) +* Rebuilt `example/android` and `example/ios` directories **Note:** this version has an implicit minimum supported version of Flutter 1.10.7. See [flutter/flutter_markdown issue #156](https://github.com/flutter/flutter_markdown/issues/156) for more detail. ## 0.3.0 - * Support GitHub flavoured Markdown - * Support strikethrough - * Convert TextSpan to use new InlineSpan API +* Support GitHub flavoured Markdown +* Support strikethrough +* Convert TextSpan to use new InlineSpan API ## 0.2.0 - * Updated environment sdk constraints to make the package +* Updated environment sdk constraints to make the package Dart 2 compatible. As a result, usage of this version and higher requires a Dart 2 SDK. ## 0.1.6 - * Updated `markdown` dependency. +* Updated `markdown` dependency. ## 0.1.5 - * Add `mockito` as a dev dependency. Eliminate use of `package:http`, which +* Add `mockito` as a dev dependency. Eliminate use of `package:http`, which is no longer part of Flutter. ## 0.1.4 - * Add `li` style to bullets +* Add `li` style to bullets ## 0.1.3 - * Add `path` and `http` as declared dependencies in `pubspec.yaml` +* Add `path` and `http` as declared dependencies in `pubspec.yaml` ## 0.1.2 - * Add support for horizontal rules. - * Fix the `onTap` callback on images nested in hyperlinks +* Add support for horizontal rules. +* Fix the `onTap` callback on images nested in hyperlinks ## 0.1.1 - * Add support for local file paths in image links. Make sure to set the +* Add support for local file paths in image links. Make sure to set the `imageDirectory` property to specify the base directory containing the image files. ## 0.1.0 - * Roll the dependency on `markdown` to 1.0.0 - * Add a test and example for image links - * Fix the `onTap` callback on hyperlinks +* Roll the dependency on `markdown` to 1.0.0 +* Add a test and example for image links +* Fix the `onTap` callback on hyperlinks ## 0.0.9 - * First published version +* First published version diff --git a/packages/flutter_markdown/example/android/.gitignore b/packages/flutter_markdown/example/android/.gitignore index 0a741cb43d6..8e599af9f21 100644 --- a/packages/flutter_markdown/example/android/.gitignore +++ b/packages/flutter_markdown/example/android/.gitignore @@ -7,5 +7,5 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties diff --git a/packages/flutter_markdown/example/android/build.gradle b/packages/flutter_markdown/example/android/build.gradle index 582d60a2faa..d13ef556e26 100644 --- a/packages/flutter_markdown/example/android/build.gradle +++ b/packages/flutter_markdown/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/flutter_markdown/example/android/settings.gradle b/packages/flutter_markdown/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/flutter_markdown/example/android/settings.gradle +++ b/packages/flutter_markdown/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index 7bd96b90dfc..0706453a01d 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -174,7 +174,6 @@ class MarkdownBuilder implements md.NodeVisitor { final List<_TableElement> _tables = <_TableElement>[]; final List<_InlineElement> _inlines = <_InlineElement>[]; final List _linkHandlers = []; - final ScrollController _preScrollController = ScrollController(); String? _currentBlockTag; String? _lastVisitedTag; bool _isInBlockquote = false; @@ -347,10 +346,11 @@ class MarkdownBuilder implements md.NodeVisitor { child = builders[_blocks.last.tag!]! .visitText(text, styleSheet.styles[_blocks.last.tag!]); } else if (_blocks.last.tag == 'pre') { + final ScrollController preScrollController = ScrollController(); child = Scrollbar( - controller: _preScrollController, + controller: preScrollController, child: SingleChildScrollView( - controller: _preScrollController, + controller: preScrollController, scrollDirection: Axis.horizontal, padding: styleSheet.codeblockPadding, child: _buildRichText(delegate.formatText(styleSheet, text.text)), @@ -438,12 +438,20 @@ class MarkdownBuilder implements md.NodeVisitor { ); } } else if (tag == 'table') { - child = Table( - defaultColumnWidth: styleSheet.tableColumnWidth!, - defaultVerticalAlignment: styleSheet.tableVerticalAlignment, - border: styleSheet.tableBorder, - children: _tables.removeLast().rows, - ); + if (styleSheet.tableColumnWidth is FixedColumnWidth) { + final ScrollController tableScrollController = ScrollController(); + child = Scrollbar( + controller: tableScrollController, + child: SingleChildScrollView( + controller: tableScrollController, + scrollDirection: Axis.horizontal, + padding: styleSheet.tablePadding, + child: _buildTable(), + ), + ); + } else { + child = _buildTable(); + } } else if (tag == 'blockquote') { _isInBlockquote = false; child = DecoratedBox( @@ -537,6 +545,8 @@ class MarkdownBuilder implements md.NodeVisitor { style: textSpan.style?.copyWith( fontFeatures: [ const FontFeature.enable('sups'), + if (styleSheet.superscriptFontFeatureTag != null) + FontFeature.enable(styleSheet.superscriptFontFeatureTag!), ], ), ), @@ -556,6 +566,15 @@ class MarkdownBuilder implements md.NodeVisitor { _lastVisitedTag = tag; } + Table _buildTable() { + return Table( + defaultColumnWidth: styleSheet.tableColumnWidth!, + defaultVerticalAlignment: styleSheet.tableVerticalAlignment, + border: styleSheet.tableBorder, + children: _tables.removeLast().rows, + ); + } + Widget _buildImage(String src, String? title, String? alt) { final List parts = src.split('#'); if (parts.isEmpty) { @@ -959,9 +978,10 @@ class MarkdownBuilder implements md.NodeVisitor { text!, textScaler: styleSheet.textScaler, textAlign: textAlign ?? TextAlign.start, - onSelectionChanged: - (TextSelection selection, SelectionChangedCause? cause) => - onSelectionChanged!(text.text, selection, cause), + onSelectionChanged: onSelectionChanged != null + ? (TextSelection selection, SelectionChangedCause? cause) => + onSelectionChanged!(text.text, selection, cause) + : null, onTap: onTapText, key: k, ); diff --git a/packages/flutter_markdown/lib/src/style_sheet.dart b/packages/flutter_markdown/lib/src/style_sheet.dart index 7fc16d9881e..3923e618ff3 100644 --- a/packages/flutter_markdown/lib/src/style_sheet.dart +++ b/packages/flutter_markdown/lib/src/style_sheet.dart @@ -38,6 +38,7 @@ class MarkdownStyleSheet { this.tableHead, this.tableBody, this.tableHeadAlign, + this.tablePadding, this.tableBorder, this.tableColumnWidth, this.tableCellsPadding, @@ -59,6 +60,7 @@ class MarkdownStyleSheet { this.orderedListAlign = WrapAlignment.start, this.blockquoteAlign = WrapAlignment.start, this.codeblockAlign = WrapAlignment.start, + this.superscriptFontFeatureTag, @Deprecated('Use textScaler instead.') this.textScaleFactor, TextScaler? textScaler, }) : assert( @@ -133,6 +135,7 @@ class MarkdownStyleSheet { tableHead: const TextStyle(fontWeight: FontWeight.w600), tableBody: theme.textTheme.bodyMedium, tableHeadAlign: TextAlign.center, + tablePadding: const EdgeInsets.only(bottom: 4.0), tableBorder: TableBorder.all( color: theme.dividerColor, ), @@ -230,6 +233,7 @@ class MarkdownStyleSheet { ), tableBody: theme.textTheme.textStyle, tableHeadAlign: TextAlign.center, + tablePadding: const EdgeInsets.only(bottom: 8), tableBorder: TableBorder.all(color: CupertinoColors.separator, width: 0), tableColumnWidth: const FlexColumnWidth(), tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8), @@ -311,6 +315,7 @@ class MarkdownStyleSheet { tableHead: const TextStyle(fontWeight: FontWeight.w600), tableBody: theme.textTheme.bodyMedium, tableHeadAlign: TextAlign.center, + tablePadding: const EdgeInsets.only(bottom: 4.0), tableBorder: TableBorder.all( color: theme.dividerColor, ), @@ -370,6 +375,7 @@ class MarkdownStyleSheet { TextStyle? tableHead, TextStyle? tableBody, TextAlign? tableHeadAlign, + EdgeInsets? tablePadding, TableBorder? tableBorder, TableColumnWidth? tableColumnWidth, EdgeInsets? tableCellsPadding, @@ -391,6 +397,7 @@ class MarkdownStyleSheet { WrapAlignment? orderedListAlign, WrapAlignment? blockquoteAlign, WrapAlignment? codeblockAlign, + String? superscriptFontFeatureTag, @Deprecated('Use textScaler instead.') double? textScaleFactor, TextScaler? textScaler, }) { @@ -434,6 +441,7 @@ class MarkdownStyleSheet { tableHead: tableHead ?? this.tableHead, tableBody: tableBody ?? this.tableBody, tableHeadAlign: tableHeadAlign ?? this.tableHeadAlign, + tablePadding: tablePadding ?? this.tablePadding, tableBorder: tableBorder ?? this.tableBorder, tableColumnWidth: tableColumnWidth ?? this.tableColumnWidth, tableCellsPadding: tableCellsPadding ?? this.tableCellsPadding, @@ -457,6 +465,8 @@ class MarkdownStyleSheet { orderedListAlign: orderedListAlign ?? this.orderedListAlign, blockquoteAlign: blockquoteAlign ?? this.blockquoteAlign, codeblockAlign: codeblockAlign ?? this.codeblockAlign, + superscriptFontFeatureTag: + superscriptFontFeatureTag ?? this.superscriptFontFeatureTag, textScaler: newTextScaler, textScaleFactor: nextTextScaleFactor, ); @@ -498,6 +508,7 @@ class MarkdownStyleSheet { tableHead: tableHead!.merge(other.tableHead), tableBody: tableBody!.merge(other.tableBody), tableHeadAlign: other.tableHeadAlign, + tablePadding: other.tablePadding, tableBorder: other.tableBorder, tableColumnWidth: other.tableColumnWidth, tableCellsPadding: other.tableCellsPadding, @@ -520,6 +531,7 @@ class MarkdownStyleSheet { blockquoteAlign: other.blockquoteAlign, codeblockAlign: other.codeblockAlign, textScaleFactor: other.textScaleFactor, + superscriptFontFeatureTag: other.superscriptFontFeatureTag, // Only one of textScaler and textScaleFactor can be passed. If // other.textScaleFactor is non-null, then the sheet was created with a // textScaleFactor and the textScaler was derived from that, so should be @@ -615,6 +627,9 @@ class MarkdownStyleSheet { /// The [TextAlign] to use for `th` elements. final TextAlign? tableHeadAlign; + /// The padding to use for `table` elements. + final EdgeInsets? tablePadding; + /// The [TableBorder] to use for `table` elements. final TableBorder? tableBorder; @@ -688,6 +703,10 @@ class MarkdownStyleSheet { @Deprecated('Use textScaler instead.') final double? textScaleFactor; + /// Custom font feature tag for font which does not support `sups' + /// feature to create superscript in footnotes. + final String? superscriptFontFeatureTag; + /// A [Map] from element name to the corresponding [TextStyle] object. Map get styles => _styles; Map _styles; @@ -731,6 +750,7 @@ class MarkdownStyleSheet { other.tableHead == tableHead && other.tableBody == tableBody && other.tableHeadAlign == tableHeadAlign && + other.tablePadding == tablePadding && other.tableBorder == tableBorder && other.tableColumnWidth == tableColumnWidth && other.tableCellsPadding == tableCellsPadding && @@ -752,6 +772,7 @@ class MarkdownStyleSheet { other.orderedListAlign == orderedListAlign && other.blockquoteAlign == blockquoteAlign && other.codeblockAlign == codeblockAlign && + other.superscriptFontFeatureTag == superscriptFontFeatureTag && other.textScaler == textScaler; } @@ -788,6 +809,7 @@ class MarkdownStyleSheet { tableHead, tableBody, tableHeadAlign, + tablePadding, tableBorder, tableColumnWidth, tableCellsPadding, @@ -811,6 +833,7 @@ class MarkdownStyleSheet { codeblockAlign, textScaler, textScaleFactor, + superscriptFontFeatureTag, ]); } } diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index b5e2c945622..ecfd21ea000 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.7.0 +version: 0.7.3 environment: sdk: ^3.3.0 diff --git a/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo.png b/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo.png index cbf90d45750..a4b3590eb09 100644 Binary files a/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo.png and b/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo.png differ diff --git a/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo_old.png b/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo_old.png new file mode 100644 index 00000000000..cbf90d45750 Binary files /dev/null and b/packages/flutter_markdown/test/assets/images/golden/image_test/custom_builder_asset_logo_old.png differ diff --git a/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo.png b/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo.png index cbf90d45750..a4b3590eb09 100644 Binary files a/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo.png and b/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo.png differ diff --git a/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo_old.png b/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo_old.png new file mode 100644 index 00000000000..cbf90d45750 Binary files /dev/null and b/packages/flutter_markdown/test/assets/images/golden/image_test/resource_asset_logo_old.png differ diff --git a/packages/flutter_markdown/test/footnote_test.dart b/packages/flutter_markdown/test/footnote_test.dart index 191c2cb6200..3be96c46093 100644 --- a/packages/flutter_markdown/test/footnote_test.dart +++ b/packages/flutter_markdown/test/footnote_test.dart @@ -158,7 +158,7 @@ void defineTests() { 'superscript textstyle replacing', () { testWidgets( - 'superscript has correct fontfeature', + 'superscript has correct default fontfeature', (WidgetTester tester) async { const String data = 'Foo[^a]\n[^a]: Bar'; await tester.pumpWidget( @@ -184,6 +184,35 @@ void defineTests() { }, ); + testWidgets( + 'superscript has correct custom fontfeature', + (WidgetTester tester) async { + const String data = 'Foo[^a]\n[^a]: Bar'; + await tester.pumpWidget( + boilerplate( + MarkdownBody( + data: data, + styleSheet: + MarkdownStyleSheet(superscriptFontFeatureTag: 'numr'), + ), + ), + ); + + final Iterable widgets = tester.allWidgets; + final Text text = + widgets.firstWhere((Widget widget) => widget is Text) as Text; + + final TextSpan span = text.textSpan! as TextSpan; + final List? children = span.children; + + expect(children, isNotNull); + expect(children!.length, 2); + expect(children[1].style, isNotNull); + expect(children[1].style!.fontFeatures?.length, 2); + expect(children[1].style!.fontFeatures?[1].feature, 'numr'); + }, + ); + testWidgets( 'superscript index has the same font style like text', (WidgetTester tester) async { diff --git a/packages/flutter_markdown/test/image_test.dart b/packages/flutter_markdown/test/image_test.dart index 00677f1b728..c6f448eb8a6 100644 --- a/packages/flutter_markdown/test/image_test.dart +++ b/packages/flutter_markdown/test/image_test.dart @@ -13,6 +13,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'image_test_mocks.dart'; import 'utils.dart'; +bool isRunningInStable = io.Platform.environment['CHANNEL'] == 'stable'; + void main() => defineTests(); void defineTests() { @@ -167,8 +169,9 @@ void defineTests() { await expectLater( find.byType(Container), - matchesGoldenFile( - 'assets/images/golden/image_test/resource_asset_logo.png')); + matchesGoldenFile(isRunningInStable + ? 'assets/images/golden/image_test/resource_asset_logo_old.png' + : 'assets/images/golden/image_test/resource_asset_logo.png')); }, skip: kIsWeb, // Goldens are platform-specific. ); @@ -413,8 +416,9 @@ void defineTests() { await expectLater( find.byType(Container), - matchesGoldenFile( - 'assets/images/golden/image_test/custom_builder_asset_logo.png')); + matchesGoldenFile(isRunningInStable + ? 'assets/images/golden/image_test/custom_builder_asset_logo_old.png' + : 'assets/images/golden/image_test/custom_builder_asset_logo.png')); }, skip: kIsWeb, // Goldens are platform-specific. ); diff --git a/packages/flutter_markdown/test/image_test_mocks.dart b/packages/flutter_markdown/test/image_test_mocks.dart index 1cd56219c1a..160adc4582a 100644 --- a/packages/flutter_markdown/test/image_test_mocks.dart +++ b/packages/flutter_markdown/test/image_test_mocks.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:mockito/mockito.dart'; @@ -69,25 +68,15 @@ MockHttpClient createMockImageHttpClient(SecurityContext? _) { return client; } -// This string represents the hexidecial bytes of a transparent image. A -// string is used to make the visual representation of the data compact. A -// List of the same data requires over 60 lines in a source file with -// each element in the array on a single line. -const String _imageBytesAsString = ''' +// A list of integers that can be consumed as image data in a stream. +final List _transparentImage = [ + // Image bytes. 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, - '''; - -// Convert the string representing the hexidecimal bytes in the image into -// a list of integers that can be consumed as image data in a stream. -final List _transparentImage = const LineSplitter() - .convert(_imageBytesAsString.replaceAllMapped( - RegExp(r' *0x([A-F0-9]{2}),? *\n? *'), (Match m) => '${m[1]}\n')) - .map((String b) => int.parse(b, radix: 16)) - .toList(); +]; List getTestImageData() { return _transparentImage; diff --git a/packages/flutter_markdown/test/scrollable_test.dart b/packages/flutter_markdown/test/scrollable_test.dart index ef8e9096766..1042a6fa98a 100644 --- a/packages/flutter_markdown/test/scrollable_test.dart +++ b/packages/flutter_markdown/test/scrollable_test.dart @@ -34,6 +34,34 @@ void defineTests() { }, ); + testWidgets( + 'two code blocks use different scroll controllers', + (WidgetTester tester) async { + const String data = + "```\nvoid main() {\n print('Hello World!');\n}\n```" + '\n' + "```\nvoid main() {\n print('Hello World!');\n}\n```"; + + await tester.pumpWidget( + boilerplate( + const MediaQuery( + data: MediaQueryData(), + child: MarkdownBody(data: data), + ), + ), + ); + + final Iterable widgets = tester.allWidgets; + final Iterable scrollViews = + widgets.whereType(); + expect(scrollViews, hasLength(2)); + expect(scrollViews.first.controller, isNotNull); + expect(scrollViews.last.controller, isNotNull); + expect(scrollViews.first.controller, + isNot(equals(scrollViews.last.controller))); + }, + ); + testWidgets( 'controller', (WidgetTester tester) async { @@ -82,5 +110,69 @@ void defineTests() { ]); }, ); + + testWidgets( + 'table', + (WidgetTester tester) async { + const String data = '|Header 1|Header 2|Header 3|' + '\n|-----|-----|-----|' + '\n|Col 1|Col 2|Col 3|'; + await tester.pumpWidget( + boilerplate( + MediaQuery( + data: const MediaQueryData(), + child: MarkdownBody( + data: data, + styleSheet: MarkdownStyleSheet( + tableColumnWidth: const FixedColumnWidth(150), + ), + ), + ), + ), + ); + + final Iterable widgets = tester.allWidgets; + final Iterable scrollViews = + widgets.whereType(); + expect(scrollViews, isNotEmpty); + expect(scrollViews.first.controller, isNotNull); + }, + ); + + testWidgets( + 'two tables use different scroll controllers', + (WidgetTester tester) async { + const String data = '|Header 1|Header 2|Header 3|' + '\n|-----|-----|-----|' + '\n|Col 1|Col 2|Col 3|' + '\n' + '\n|Header 1|Header 2|Header 3|' + '\n|-----|-----|-----|' + '\n|Col 1|Col 2|Col 3|'; + + await tester.pumpWidget( + boilerplate( + MediaQuery( + data: const MediaQueryData(), + child: MarkdownBody( + data: data, + styleSheet: MarkdownStyleSheet( + tableColumnWidth: const FixedColumnWidth(150), + ), + ), + ), + ), + ); + + final Iterable widgets = tester.allWidgets; + final Iterable scrollViews = + widgets.whereType(); + expect(scrollViews, hasLength(2)); + expect(scrollViews.first.controller, isNotNull); + expect(scrollViews.last.controller, isNotNull); + expect(scrollViews.first.controller, + isNot(equals(scrollViews.last.controller))); + }, + ); }); } diff --git a/packages/flutter_markdown/test/text_test.dart b/packages/flutter_markdown/test/text_test.dart index 27f16cc9004..e8e8835eb2b 100644 --- a/packages/flutter_markdown/test/text_test.dart +++ b/packages/flutter_markdown/test/text_test.dart @@ -284,6 +284,50 @@ void defineTests() { }, ); + testWidgets( + 'Selectable without onSelectionChanged', + (WidgetTester tester) async { + const String data = '# abc def ghi\njkl opq'; + + await tester.pumpWidget( + const MaterialApp( + home: Material( + child: MarkdownBody( + data: data, + selectable: true, + ), + ), + ), + ); + + // Find the positions before character 'd' and 'f'. + final Offset dPos = positionInRenderedText(tester, 'abc def ghi', 4); + final Offset fPos = positionInRenderedText(tester, 'abc def ghi', 6); + // Select from 'd' until 'f'. + final TestGesture firstGesture = + await tester.startGesture(dPos, kind: PointerDeviceKind.mouse); + addTearDown(firstGesture.removePointer); + await tester.pump(); + await firstGesture.moveTo(fPos); + await firstGesture.up(); + await tester.pump(); + + // Find the positions before character 'j' and 'o'. + final Offset jPos = positionInRenderedText(tester, 'jkl opq', 0); + final Offset oPos = positionInRenderedText(tester, 'jkl opq', 4); + // Select from 'j' until 'o'. + final TestGesture secondGesture = + await tester.startGesture(jPos, kind: PointerDeviceKind.mouse); + addTearDown(secondGesture.removePointer); + await tester.pump(); + await secondGesture.moveTo(oPos); + await secondGesture.up(); + await tester.pump(); + + expect(tester.takeException(), isNull); + }, + ); + testWidgets( 'header with line of text and onSelectionChanged callback', (WidgetTester tester) async { diff --git a/packages/flutter_migrate/CHANGELOG.md b/packages/flutter_migrate/CHANGELOG.md index 03b422057f7..84ae083e470 100644 --- a/packages/flutter_migrate/CHANGELOG.md +++ b/packages/flutter_migrate/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.0.1+3 diff --git a/packages/flutter_migrate/lib/src/base/context.dart b/packages/flutter_migrate/lib/src/base/context.dart index ea1dd4d83d7..49e8943b346 100644 --- a/packages/flutter_migrate/lib/src/base/context.dart +++ b/packages/flutter_migrate/lib/src/base/context.dart @@ -118,7 +118,7 @@ class AppContext { T? get() { dynamic value = _generateIfNecessary(T, _overrides); if (value == null && _parent != null) { - value = _parent!.get(); + value = _parent.get(); } return _unboxNull(value ?? _generateIfNecessary(T, _fallbacks)) as T?; } diff --git a/packages/flutter_migrate/pubspec.yaml b/packages/flutter_migrate/pubspec.yaml index d8dafc6b739..e0aea7889b2 100644 --- a/packages/flutter_migrate/pubspec.yaml +++ b/packages/flutter_migrate/pubspec.yaml @@ -6,21 +6,21 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ publish_to: none environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: args: ^2.3.1 - convert: 3.0.2 - file: 6.1.4 - intl: 0.17.0 - meta: 1.8.0 + convert: ^3.0.2 + file: ">=6.0.0 <8.0.0" + intl: ">=0.17.0 <0.20.0" + meta: ^1.8.0 path: ^1.8.0 - process: 4.2.4 - vm_service: 9.3.0 - yaml: 3.1.1 + process: ^4.2.4 + vm_service: ^9.3.0 + yaml: ^3.1.1 dev_dependencies: - collection: 1.16.0 + collection: ^1.16.0 file_testing: 3.0.0 lints: ^2.0.0 test: ^1.16.0 diff --git a/packages/flutter_migrate/test/migrate_test.dart b/packages/flutter_migrate/test/migrate_test.dart index beeddb329c8..f07085bfbd5 100644 --- a/packages/flutter_migrate/test/migrate_test.dart +++ b/packages/flutter_migrate/test/migrate_test.dart @@ -382,10 +382,10 @@ flutter: - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. + # https://flutter.dev/to/resolution-aware-images. # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages + # https://flutter.dev/to/asset-from-package # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a @@ -405,7 +405,7 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + # see https://flutter.dev/to/font-from-package ''', flush: true); diff --git a/packages/flutter_migrate/test/src/io.dart b/packages/flutter_migrate/test/src/io.dart index c7e6bf3ec93..a111ccdcffd 100644 --- a/packages/flutter_migrate/test/src/io.dart +++ b/packages/flutter_migrate/test/src/io.dart @@ -27,7 +27,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.createDirectory(path); } - return _fileSystemDelegate!.directory(path); + return _fileSystemDelegate.directory(path); } @override @@ -35,7 +35,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.createFile(path); } - return _fileSystemDelegate!.file(path); + return _fileSystemDelegate.file(path); } @override @@ -43,7 +43,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.createLink(path); } - return _fileSystemDelegate!.link(path); + return _fileSystemDelegate.link(path); } @override @@ -51,7 +51,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.fsWatch(path, events, recursive); } - return _fileSystemDelegate! + return _fileSystemDelegate .file(path) .watch(events: events, recursive: recursive); } @@ -61,7 +61,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.fsWatchIsSupported(); } - return _fileSystemDelegate!.isWatchSupported; + return _fileSystemDelegate.isWatchSupported; } @override @@ -69,7 +69,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.fseGetType(path, followLinks); } - return _fileSystemDelegate!.type(path, followLinks: followLinks); + return _fileSystemDelegate.type(path, followLinks: followLinks); } @override @@ -77,7 +77,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.fseGetTypeSync(path, followLinks); } - return _fileSystemDelegate!.typeSync(path, followLinks: followLinks); + return _fileSystemDelegate.typeSync(path, followLinks: followLinks); } @override @@ -85,7 +85,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.fseIdentical(path1, path2); } - return _fileSystemDelegate!.identical(path1, path2); + return _fileSystemDelegate.identical(path1, path2); } @override @@ -93,7 +93,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.fseIdenticalSync(path1, path2); } - return _fileSystemDelegate!.identicalSync(path1, path2); + return _fileSystemDelegate.identicalSync(path1, path2); } @override @@ -101,7 +101,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.getCurrentDirectory(); } - return _fileSystemDelegate!.currentDirectory; + return _fileSystemDelegate.currentDirectory; } @override @@ -109,7 +109,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.getSystemTempDirectory(); } - return _fileSystemDelegate!.systemTempDirectory; + return _fileSystemDelegate.systemTempDirectory; } @override @@ -117,7 +117,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.setCurrentDirectory(path); } - _fileSystemDelegate!.currentDirectory = path; + _fileSystemDelegate.currentDirectory = path; } @override @@ -125,7 +125,7 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.stat(path); } - return _fileSystemDelegate!.stat(path); + return _fileSystemDelegate.stat(path); } @override @@ -133,6 +133,6 @@ class FlutterIOOverrides extends io.IOOverrides { if (_fileSystemDelegate == null) { return super.statSync(path); } - return _fileSystemDelegate!.statSync(path); + return _fileSystemDelegate.statSync(path); } } diff --git a/packages/flutter_migrate/test/test_data/migrate_project.dart b/packages/flutter_migrate/test/test_data/migrate_project.dart index 4f3b4023e2c..2590834a4c5 100644 --- a/packages/flutter_migrate/test/test_data/migrate_project.dart +++ b/packages/flutter_migrate/test/test_data/migrate_project.dart @@ -278,10 +278,10 @@ flutter: - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. + # https://flutter.dev/to/resolution-aware-images. # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages + # https://flutter.dev/to/asset-from-package # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a @@ -301,7 +301,7 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + # see https://flutter.dev/to/font-from-package '''; } diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md index bafd1afc09c..b7082c02255 100644 --- a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md +++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.20 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 2.0.19 * Updates minSdkVersion to 19. diff --git a/packages/flutter_plugin_android_lifecycle/README.md b/packages/flutter_plugin_android_lifecycle/README.md index 6db96fb85e7..69a5be88f9d 100644 --- a/packages/flutter_plugin_android_lifecycle/README.md +++ b/packages/flutter_plugin_android_lifecycle/README.md @@ -13,10 +13,6 @@ major version of the Android `Lifecycle` API they expect. |-------------|---------| | **Support** | SDK 16+ | -## Installation - -Add `flutter_plugin_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). - ## Example Use a `FlutterLifecycleAdapter` within another Flutter plugin's Android implementation, as shown diff --git a/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java b/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java index 800f3b594d6..cf6972da15b 100644 --- a/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java +++ b/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java @@ -14,11 +14,6 @@ *

DO NOT USE THIS CLASS. */ public class FlutterAndroidLifecyclePlugin implements FlutterPlugin { - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - // no-op - } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/flutter_plugin_android_lifecycle/example/android/build.gradle b/packages/flutter_plugin_android_lifecycle/example/android/build.gradle index dc5a8d1453e..ce8d2392c8c 100644 --- a/packages/flutter_plugin_android_lifecycle/example/android/build.gradle +++ b/packages/flutter_plugin_android_lifecycle/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle b/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle +++ b/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml index ce25da74efb..98ca0aebd4d 100644 --- a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the flutter_plugin_android_lifecycle plugin publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/flutter_plugin_android_lifecycle/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/pubspec.yaml index 7d70e4aa84d..30e358ac15b 100644 --- a/packages/flutter_plugin_android_lifecycle/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/pubspec.yaml @@ -2,11 +2,11 @@ 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.19 +version: 2.0.20 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/flutter_template_images/CHANGELOG.md b/packages/flutter_template_images/CHANGELOG.md index 3d5ff3ede52..69b55d39700 100644 --- a/packages/flutter_template_images/CHANGELOG.md +++ b/packages/flutter_template_images/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 4.2.1 diff --git a/packages/flutter_template_images/pubspec.yaml b/packages/flutter_template_images/pubspec.yaml index 299aecef3cf..b046675efae 100644 --- a/packages/flutter_template_images/pubspec.yaml +++ b/packages/flutter_template_images/pubspec.yaml @@ -5,7 +5,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 4.2.1 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 topics: - assets diff --git a/packages/go_router/AUTHORS b/packages/go_router/AUTHORS index fa2a9bee3ba..7174a736ef5 100644 --- a/packages/go_router/AUTHORS +++ b/packages/go_router/AUTHORS @@ -5,3 +5,4 @@ Google Inc. csells@sellsbrothers.com +Hashir Shoaib diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 576a71789a7..c8eaab75a70 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,40 @@ +## 14.2.0 + +- Added proper `redirect` handling for `ShellRoute.$route` and `StatefulShellRoute.$route` for proper redirection handling in case of code generation. + +## 14.1.4 + +- Fixes a URL in `navigation.md`. + +## 14.1.3 + +- Improves the logging of routes when `debugLogDiagnostics` is enabled or `debugKnownRoutes() is called. Explains the position of shell routes in the route tree. Prints the widget name of the routes it is building. + +## 14.1.2 + +- Fixes issue that path parameters are not set when using the `goBranch`. + +## 14.1.1 + +- Fixes correctness of the state provided in the `onExit`. + +## 14.1.0 + +- Adds route redirect to ShellRoutes + +## 14.0.2 + +- Fixes unwanted logs when `hierarchicalLoggingEnabled` was set to `true`. + +## 14.0.1 + +- Updates the redirection documentation for clarity + +## 14.0.0 + +- **BREAKING CHANGE** + - `GoRouteData`'s `onExit` now takes 2 parameters `BuildContext context, GoRouterState state`. + ## 13.2.4 - Updates examples to use uri.path instead of uri.toString() for accessing the current location. @@ -26,7 +63,7 @@ ## 13.0.1 -* Fixes new lint warnings. +- Fixes new lint warnings. ## 13.0.0 @@ -37,12 +74,12 @@ ## 12.1.3 -* Fixes a typo in `navigation.md`. +- Fixes a typo in `navigation.md`. ## 12.1.2 -* Fixes an incorrect use of `extends` for Dart 3 compatibility. -* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. +- Fixes an incorrect use of `extends` for Dart 3 compatibility. +- Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. ## 12.1.1 @@ -117,7 +154,7 @@ ## 10.1.2 -* Adds pub topics to package metadata. +- Adds pub topics to package metadata. ## 10.1.1 @@ -448,7 +485,7 @@ - Fixes a bug where intermediate route redirect methods are not called. - GoRouter implements the RouterConfig interface, allowing you to call - MaterialApp.router(routerConfig: _myGoRouter) instead of passing + MaterialApp.router(routerConfig: \_myGoRouter) instead of passing the RouterDelegate, RouteInformationParser, and RouteInformationProvider fields. - **BREAKING CHANGE** diff --git a/packages/go_router/README.md b/packages/go_router/README.md index a9f9b6b2711..7d506d0c346 100644 --- a/packages/go_router/README.md +++ b/packages/go_router/README.md @@ -37,6 +37,7 @@ See the API documentation for details on the following topics: - [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html) ## Migration Guides +- [Migrating to 14.0.0](https://flutter.dev/go/go-router-v14-breaking-changes). - [Migrating to 13.0.0](https://flutter.dev/go/go-router-v13-breaking-changes). - [Migrating to 12.0.0](https://flutter.dev/go/go-router-v12-breaking-changes). - [Migrating to 11.0.0](https://flutter.dev/go/go-router-v11-breaking-changes). diff --git a/packages/go_router/doc/navigation.md b/packages/go_router/doc/navigation.md index 74bc86631ca..af7d1b3bb1a 100644 --- a/packages/go_router/doc/navigation.md +++ b/packages/go_router/doc/navigation.md @@ -86,8 +86,7 @@ screen along with the shell is placed entirely on top of the current screen. ![An animation shows pushing a new screen with the different shell as current screen](https://flutter.github.io/assets-for-api-docs/assets/go_router/push_different_shell.gif) To try out the behavior yourself, see -[push_with_shell_route.dart](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/extra_codec.dart). - +[push_with_shell_route.dart](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/push_with_shell_route.dart). ## Returning values Waiting for a value to be returned: diff --git a/packages/go_router/doc/redirection.md b/packages/go_router/doc/redirection.md index e0ff022005a..c8398e028ae 100644 --- a/packages/go_router/doc/redirection.md +++ b/packages/go_router/doc/redirection.md @@ -10,7 +10,7 @@ either the GoRouter or GoRoute constructor: ```dart redirect: (BuildContext context, GoRouterState state) { - if (AuthState.of(context).isSignedIn) { + if (!AuthState.of(context).isSignedIn) { return '/signin'; } else { return null; diff --git a/packages/go_router/example/android/.gitignore b/packages/go_router/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/go_router/example/android/.gitignore +++ b/packages/go_router/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/go_router/example/android/build.gradle b/packages/go_router/example/android/build.gradle index 228f12cefbc..8a2e9e183dd 100644 --- a/packages/go_router/example/android/build.gradle +++ b/packages/go_router/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/go_router/example/android/settings.gradle b/packages/go_router/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/go_router/example/android/settings.gradle +++ b/packages/go_router/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/go_router/example/lib/on_exit.dart b/packages/go_router/example/lib/on_exit.dart index fba83a7d1fd..bf397efedd3 100644 --- a/packages/go_router/example/lib/on_exit.dart +++ b/packages/go_router/example/lib/on_exit.dart @@ -22,7 +22,10 @@ final GoRouter _router = GoRouter( builder: (BuildContext context, GoRouterState state) { return const DetailsScreen(); }, - onExit: (BuildContext context) async { + onExit: ( + BuildContext context, + GoRouterState state, + ) async { final bool? confirmed = await showDialog( context: context, builder: (_) { diff --git a/packages/go_router/lib/fix_data.yaml b/packages/go_router/lib/fix_data.yaml index 6a20a7de55a..9770fd40004 100644 --- a/packages/go_router/lib/fix_data.yaml +++ b/packages/go_router/lib/fix_data.yaml @@ -3,7 +3,7 @@ # found in the LICENSE file. # For details regarding the *Flutter Fix* feature, see -# https://flutter.dev/docs/development/tools/flutter-fix +# https://flutter.dev/to/flutter-fix # Please add new fixes to the top of the file, separated by one blank line # from other fixes. In a comment, include a link to the PR where the change diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart index 09764747aae..d1cca5ce61f 100644 --- a/packages/go_router/lib/src/configuration.dart +++ b/packages/go_router/lib/src/configuration.dart @@ -394,14 +394,13 @@ class RouteConfiguration { return prevMatchList; } - final List routeMatches = []; + final List routeMatches = []; prevMatchList.visitRouteMatches((RouteMatchBase match) { - if (match is RouteMatch) { + if (match.route.redirect != null) { routeMatches.add(match); } return true; }); - final FutureOr routeLevelRedirectResult = _getRouteLevelRedirect(context, prevMatchList, routeMatches, 0); @@ -434,25 +433,22 @@ class RouteConfiguration { FutureOr _getRouteLevelRedirect( BuildContext context, RouteMatchList matchList, - List routeMatches, + List routeMatches, int currentCheckIndex, ) { if (currentCheckIndex >= routeMatches.length) { return null; } - final RouteMatch match = routeMatches[currentCheckIndex]; + final RouteMatchBase match = routeMatches[currentCheckIndex]; FutureOr processRouteRedirect(String? newLocation) => newLocation ?? _getRouteLevelRedirect( context, matchList, routeMatches, currentCheckIndex + 1); - final GoRoute route = match.route; - FutureOr routeRedirectResult; - if (route.redirect != null) { - routeRedirectResult = route.redirect!( - context, - match.buildState(this, matchList), - ); - } + final RouteBase route = match.route; + final FutureOr routeRedirectResult = route.redirect!.call( + context, + match.buildState(this, matchList), + ); if (routeRedirectResult is String?) { return processRouteRedirect(routeRedirectResult); } @@ -529,7 +525,8 @@ class RouteConfiguration { String debugKnownRoutes() { final StringBuffer sb = StringBuffer(); sb.writeln('Full paths for routes:'); - _debugFullPathsFor(_routingConfig.value.routes, '', 0, sb); + _debugFullPathsFor( + _routingConfig.value.routes, '', const <_DecorationType>[], sb); if (_nameToPath.isNotEmpty) { sb.writeln('known full paths for route names:'); @@ -542,15 +539,50 @@ class RouteConfiguration { } void _debugFullPathsFor(List routes, String parentFullpath, - int depth, StringBuffer sb) { - for (final RouteBase route in routes) { + List<_DecorationType> parentDecoration, StringBuffer sb) { + for (final (int index, RouteBase route) in routes.indexed) { + final List<_DecorationType> decoration = + _getDecoration(parentDecoration, index, routes.length); + final String decorationString = + decoration.map((_DecorationType e) => e.toString()).join(); + String path = parentFullpath; if (route is GoRoute) { - final String fullPath = concatenatePaths(parentFullpath, route.path); - sb.writeln(' => ${''.padLeft(depth * 2)}$fullPath'); - _debugFullPathsFor(route.routes, fullPath, depth + 1, sb); + path = concatenatePaths(parentFullpath, route.path); + final String? screenName = + route.builder?.runtimeType.toString().split('=> ').last; + sb.writeln('$decorationString$path ' + '${screenName == null ? '' : '($screenName)'}'); } else if (route is ShellRouteBase) { - _debugFullPathsFor(route.routes, parentFullpath, depth, sb); + sb.writeln('$decorationString (ShellRoute)'); } + _debugFullPathsFor(route.routes, path, decoration, sb); + } + } + + List<_DecorationType> _getDecoration( + List<_DecorationType> parentDecoration, + int index, + int length, + ) { + final Iterable<_DecorationType> newDecoration = + parentDecoration.map((_DecorationType e) { + switch (e) { + // swap + case _DecorationType.branch: + return _DecorationType.parentBranch; + case _DecorationType.leaf: + return _DecorationType.none; + // no swap + case _DecorationType.parentBranch: + return _DecorationType.parentBranch; + case _DecorationType.none: + return _DecorationType.none; + } + }); + if (index == length - 1) { + return <_DecorationType>[...newDecoration, _DecorationType.leaf]; + } else { + return <_DecorationType>[...newDecoration, _DecorationType.branch]; } } @@ -579,3 +611,18 @@ class RouteConfiguration { } } } + +enum _DecorationType { + parentBranch('│ '), + branch('├─'), + leaf('└─'), + none(' '), + ; + + const _DecorationType(this.value); + + final String value; + + @override + String toString() => value; +} diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index 3b01b9e381c..eba3ef0c801 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -66,13 +66,17 @@ class GoRouterDelegate extends RouterDelegate } walker = walker.matches.last; } + assert(walker is RouteMatch); if (state != null) { return state.maybePop(); } // This should be the only place where the last GoRoute exit the screen. final GoRoute lastRoute = currentConfiguration.last.route; if (lastRoute.onExit != null && navigatorKey.currentContext != null) { - return !(await lastRoute.onExit!(navigatorKey.currentContext!)); + return !(await lastRoute.onExit!( + navigatorKey.currentContext!, + walker.buildState(_configuration, currentConfiguration), + )); } return false; } @@ -137,8 +141,10 @@ class GoRouterDelegate extends RouterDelegate // a microtask in case the onExit callback want to launch dialog or other // navigator operations. scheduleMicrotask(() async { - final bool onExitResult = - await routeBase.onExit!(navigatorKey.currentContext!); + final bool onExitResult = await routeBase.onExit!( + navigatorKey.currentContext!, + match.buildState(_configuration, currentConfiguration), + ); if (onExitResult) { _completeRouteMatch(result, match); } @@ -147,8 +153,12 @@ class GoRouterDelegate extends RouterDelegate } void _completeRouteMatch(Object? result, RouteMatchBase match) { - if (match is ImperativeRouteMatch) { - match.complete(result); + RouteMatchBase walker = match; + while (walker is ShellRouteMatch) { + walker = walker.matches.last; + } + if (walker is ImperativeRouteMatch) { + walker.complete(result); } currentConfiguration = currentConfiguration.remove(match); notifyListeners(); @@ -217,14 +227,13 @@ class GoRouterDelegate extends RouterDelegate } if (indexOfFirstDiff < currentGoRouteMatches.length) { - final List exitingGoRoutes = currentGoRouteMatches - .sublist(indexOfFirstDiff) - .map((RouteMatch match) => match.route) - .whereType() - .toList(); - return _callOnExitStartsAt(exitingGoRoutes.length - 1, - context: navigatorContext, routes: exitingGoRoutes) - .then((bool exit) { + final List exitingMatches = + currentGoRouteMatches.sublist(indexOfFirstDiff).toList(); + return _callOnExitStartsAt( + exitingMatches.length - 1, + context: navigatorContext, + matches: exitingMatches, + ).then((bool exit) { if (!exit) { return SynchronousFuture(null); } @@ -240,24 +249,39 @@ class GoRouterDelegate extends RouterDelegate /// /// The returned future resolves to true if all routes below the index all /// return true. Otherwise, the returned future resolves to false. - static Future _callOnExitStartsAt(int index, - {required BuildContext context, required List routes}) { + Future _callOnExitStartsAt( + int index, { + required BuildContext context, + required List matches, + }) { if (index < 0) { return SynchronousFuture(true); } - final GoRoute goRoute = routes[index]; + final RouteMatch match = matches[index]; + final GoRoute goRoute = match.route; if (goRoute.onExit == null) { - return _callOnExitStartsAt(index - 1, context: context, routes: routes); + return _callOnExitStartsAt( + index - 1, + context: context, + matches: matches, + ); } Future handleOnExitResult(bool exit) { if (exit) { - return _callOnExitStartsAt(index - 1, context: context, routes: routes); + return _callOnExitStartsAt( + index - 1, + context: context, + matches: matches, + ); } return SynchronousFuture(false); } - final FutureOr exitFuture = goRoute.onExit!(context); + final FutureOr exitFuture = goRoute.onExit!( + context, + match.buildState(_configuration, currentConfiguration), + ); if (exitFuture is bool) { return handleOnExitResult(exitFuture); } diff --git a/packages/go_router/lib/src/logging.dart b/packages/go_router/lib/src/logging.dart index 7f0a8ce5a7a..4f116422181 100644 --- a/packages/go_router/lib/src/logging.dart +++ b/packages/go_router/lib/src/logging.dart @@ -28,7 +28,7 @@ StreamSubscription? _subscription; void setLogging({bool enabled = false}) { _subscription?.cancel(); _enabled = enabled; - if (!enabled) { + if (!enabled || hierarchicalLoggingEnabled) { return; } @@ -47,16 +47,28 @@ void setLogging({bool enabled = false}) { ), ); } else { - developer.log( - e.message, - time: e.time, - sequenceNumber: e.sequenceNumber, - level: e.level.value, - name: e.loggerName, - zone: e.zone, - error: e.error, - stackTrace: e.stackTrace, - ); + _developerLogFunction(e); } }); } + +void _developerLog(LogRecord record) { + developer.log( + record.message, + time: record.time, + sequenceNumber: record.sequenceNumber, + level: record.level.value, + name: record.loggerName, + zone: record.zone, + error: record.error, + stackTrace: record.stackTrace, + ); +} + +/// A function that can be set during test to mock the developer log function. +@visibleForTesting +void Function(LogRecord)? testDeveloperLog; + +/// The function used to log messages. +void Function(LogRecord) get _developerLogFunction => + testDeveloperLog ?? _developerLog; diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index bffcd1b15b6..b4115a1fca1 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -72,6 +72,8 @@ class GoRouteInformationParser extends RouteInformationParser { return debugParserFuture = _redirect(context, matchList) .then((RouteMatchList value) { if (value.isError && onParserException != null) { + // TODO(chunhtai): Figure out what to return if context is invalid. + // ignore: use_build_context_synchronously return onParserException!(context, value); } return value; @@ -106,6 +108,8 @@ class GoRouteInformationParser extends RouteInformationParser { initialMatches, ).then((RouteMatchList matchList) { if (matchList.isError && onParserException != null) { + // TODO(chunhtai): Figure out what to return if context is invalid. + // ignore: use_build_context_synchronously return onParserException!(context, matchList); } diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 2fa4bd04640..8b23e54f31a 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -63,7 +63,8 @@ typedef NavigatorBuilder = Widget Function( /// /// If the return value is true or the future resolve to true, the route will /// exit as usual. Otherwise, the operation will abort. -typedef ExitCallback = FutureOr Function(BuildContext context); +typedef ExitCallback = FutureOr Function( + BuildContext context, GoRouterState state); /// The base class for [GoRoute] and [ShellRoute]. /// @@ -151,10 +152,67 @@ typedef ExitCallback = FutureOr Function(BuildContext context); @immutable abstract class RouteBase with Diagnosticable { const RouteBase._({ + this.redirect, required this.routes, required this.parentNavigatorKey, }); + /// An optional redirect function for this route. + /// + /// In the case that you like to make a redirection decision for a specific + /// route (or sub-route), consider doing so by passing a redirect function to + /// the GoRoute constructor. + /// + /// For example: + /// ``` + /// final GoRouter _router = GoRouter( + /// routes: [ + /// GoRoute( + /// path: '/', + /// redirect: (_) => '/family/${Families.data[0].id}', + /// ), + /// GoRoute( + /// path: '/family/:fid', + /// pageBuilder: (BuildContext context, GoRouterState state) => ..., + /// ), + /// ], + /// ); + /// ``` + /// + /// If there are multiple redirects in the matched routes, the parent route's + /// redirect takes priority over sub-route's. + /// + /// For example: + /// ``` + /// final GoRouter _router = GoRouter( + /// routes: [ + /// GoRoute( + /// path: '/', + /// redirect: (_) => '/page1', // this takes priority over the sub-route. + /// routes: [ + /// GoRoute( + /// path: 'child', + /// redirect: (_) => '/page2', + /// ), + /// ], + /// ), + /// ], + /// ); + /// ``` + /// + /// The `context.go('/child')` will be redirected to `/page1` instead of + /// `/page2`. + /// + /// Redirect can also be used for conditionally preventing users from visiting + /// routes, also known as route guards. One canonical example is user + /// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart) + /// for a complete runnable example. + /// + /// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the + /// redirection (which is how `of` method is usually implemented), a + /// re-evaluation will be triggered if the [InheritedWidget] changes. + final GoRouterRedirect? redirect; + /// The list of child routes associated with this route. final List routes; @@ -208,7 +266,7 @@ class GoRoute extends RouteBase { this.builder, this.pageBuilder, super.parentNavigatorKey, - this.redirect, + super.redirect, this.onExit, super.routes = const [], }) : assert(path.isNotEmpty, 'GoRoute path cannot be empty'), @@ -324,62 +382,6 @@ class GoRoute extends RouteBase { /// final GoRouterWidgetBuilder? builder; - /// An optional redirect function for this route. - /// - /// In the case that you like to make a redirection decision for a specific - /// route (or sub-route), consider doing so by passing a redirect function to - /// the GoRoute constructor. - /// - /// For example: - /// ``` - /// final GoRouter _router = GoRouter( - /// routes: [ - /// GoRoute( - /// path: '/', - /// redirect: (_) => '/family/${Families.data[0].id}', - /// ), - /// GoRoute( - /// path: '/family/:fid', - /// pageBuilder: (BuildContext context, GoRouterState state) => ..., - /// ), - /// ], - /// ); - /// ``` - /// - /// If there are multiple redirects in the matched routes, the parent route's - /// redirect takes priority over sub-route's. - /// - /// For example: - /// ``` - /// final GoRouter _router = GoRouter( - /// routes: [ - /// GoRoute( - /// path: '/', - /// redirect: (_) => '/page1', // this takes priority over the sub-route. - /// routes: [ - /// GoRoute( - /// path: 'child', - /// redirect: (_) => '/page2', - /// ), - /// ], - /// ), - /// ], - /// ); - /// ``` - /// - /// The `context.go('/child')` will be redirected to `/page1` instead of - /// `/page2`. - /// - /// Redirect can also be used for conditionally preventing users from visiting - /// routes, also known as route guards. One canonical example is user - /// authentication. See [Redirection](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart) - /// for a complete runnable example. - /// - /// If [BuildContext.dependOnInheritedWidgetOfExactType] is used during the - /// redirection (which is how `of` method is usually implemented), a - /// re-evaluation will be triggered if the [InheritedWidget] changes. - final GoRouterRedirect? redirect; - /// Called when this route is removed from GoRouter's route history. /// /// Some example this callback may be called: @@ -457,9 +459,11 @@ class GoRoute extends RouteBase { /// as [ShellRoute] and [StatefulShellRoute]. abstract class ShellRouteBase extends RouteBase { /// Constructs a [ShellRouteBase]. - const ShellRouteBase._( - {required super.routes, required super.parentNavigatorKey}) - : super._(); + const ShellRouteBase._({ + super.redirect, + required super.routes, + required super.parentNavigatorKey, + }) : super._(); static void _debugCheckSubRouteParentNavigatorKeys( List subRoutes, GlobalKey navigatorKey) { @@ -622,6 +626,7 @@ class ShellRouteContext { class ShellRoute extends ShellRouteBase { /// Constructs a [ShellRoute]. ShellRoute({ + super.redirect, this.builder, this.pageBuilder, this.observers, @@ -782,6 +787,7 @@ class StatefulShellRoute extends ShellRouteBase { /// [navigatorContainerBuilder]. StatefulShellRoute({ required this.branches, + super.redirect, this.builder, this.pageBuilder, required this.navigatorContainerBuilder, @@ -808,12 +814,14 @@ class StatefulShellRoute extends ShellRouteBase { /// for a complete runnable example using StatefulShellRoute.indexedStack. StatefulShellRoute.indexedStack({ required List branches, + GoRouterRedirect? redirect, StatefulShellRouteBuilder? builder, GlobalKey? parentNavigatorKey, StatefulShellRoutePageBuilder? pageBuilder, String? restorationScopeId, }) : this( branches: branches, + redirect: redirect, builder: builder, pageBuilder: pageBuilder, parentNavigatorKey: parentNavigatorKey, @@ -1126,7 +1134,12 @@ class StatefulNavigationShell extends StatefulWidget { /// Recursively traverses the routes of the provided StackedShellBranch to /// find the first GoRoute, from which a full path will be derived. final GoRoute route = branch.defaultRoute!; - return _router.configuration.locationForRoute(route)!; + final List parameters = []; + patternToRegExp(route.path, parameters); + assert(parameters.isEmpty); + final String fullPath = _router.configuration.locationForRoute(route)!; + return patternToPath( + fullPath, shellRouteContext.routerState.pathParameters); } } diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 26a6fc42a96..afe6159cecb 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -63,6 +63,11 @@ abstract class GoRouteData extends RouteData { /// Corresponds to [GoRoute.redirect]. FutureOr redirect(BuildContext context, GoRouterState state) => null; + /// Called when this route is removed from GoRouter's route history. + /// + /// Corresponds to [GoRoute.onExit]. + FutureOr onExit(BuildContext context, GoRouterState state) => true; + /// A helper function used by generated code. /// /// Should not be used directly. @@ -106,6 +111,9 @@ abstract class GoRouteData extends RouteData { FutureOr redirect(BuildContext context, GoRouterState state) => factoryImpl(state).redirect(context, state); + FutureOr onExit(BuildContext context, GoRouterState state) => + factoryImpl(state).onExit(context, state); + return GoRoute( path: path, name: name, @@ -114,6 +122,7 @@ abstract class GoRouteData extends RouteData { redirect: redirect, routes: routes, parentNavigatorKey: parentNavigatorKey, + onExit: onExit, ); } @@ -150,6 +159,14 @@ abstract class ShellRouteData extends RouteData { 'One of `builder` or `pageBuilder` must be implemented.', ); + /// An optional redirect function for this route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.redirect]. + FutureOr redirect(BuildContext context, GoRouterState state) => null; + /// A helper function used by generated code. /// /// Should not be used directly. @@ -165,6 +182,9 @@ abstract class ShellRouteData extends RouteData { return (_stateObjectExpando[state] ??= factory(state)) as T; } + FutureOr redirect(BuildContext context, GoRouterState state) => + factoryImpl(state).redirect(context, state); + Widget builder( BuildContext context, GoRouterState state, @@ -195,6 +215,7 @@ abstract class ShellRouteData extends RouteData { navigatorKey: navigatorKey, observers: observers, restorationScopeId: restorationScopeId, + redirect: redirect, ); } @@ -212,6 +233,14 @@ abstract class StatefulShellRouteData extends RouteData { /// Default const constructor const StatefulShellRouteData(); + /// An optional redirect function for this route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.redirect]. + FutureOr redirect(BuildContext context, GoRouterState state) => null; + /// [pageBuilder] is used to build the page Page pageBuilder( BuildContext context, @@ -266,6 +295,9 @@ abstract class StatefulShellRouteData extends RouteData { navigationShell, ); + FutureOr redirect(BuildContext context, GoRouterState state) => + factoryImpl(state).redirect(context, state); + if (navigatorContainerBuilder != null) { return StatefulShellRoute( branches: branches, @@ -274,6 +306,7 @@ abstract class StatefulShellRouteData extends RouteData { navigatorContainerBuilder: navigatorContainerBuilder, parentNavigatorKey: parentNavigatorKey, restorationScopeId: restorationScopeId, + redirect: redirect, ); } return StatefulShellRoute.indexedStack( @@ -282,6 +315,7 @@ abstract class StatefulShellRouteData extends RouteData { pageBuilder: pageBuilder, parentNavigatorKey: parentNavigatorKey, restorationScopeId: restorationScopeId, + redirect: redirect, ); } diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 29dfaa27a44..eec6806cf6d 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: 13.2.4 +version: 14.2.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/configuration_test.dart b/packages/go_router/test/configuration_test.dart index a35f2b87e81..0929756599f 100644 --- a/packages/go_router/test/configuration_test.dart +++ b/packages/go_router/test/configuration_test.dart @@ -1070,15 +1070,17 @@ void main() { }, ).debugKnownRoutes(), 'Full paths for routes:\n' - ' => /a\n' - ' => /a/b\n' - ' => /a/c\n' - ' => /d\n' - ' => /d/e\n' - ' => /d/e/f\n' - ' => /g\n' - ' => /g/h\n' - ' => /g/i\n', + '├─/a (Widget)\n' + '│ └─ (ShellRoute)\n' + '│ ├─/a/b (Widget)\n' + '│ └─/a/c (Widget)\n' + '├─/d (Widget)\n' + '│ └─/d/e (Widget)\n' + '│ └─/d/e/f (Widget)\n' + '└─/g (Widget)\n' + ' └─ (ShellRoute)\n' + ' ├─/g/h (Widget)\n' + ' └─/g/i (Widget)\n', ); }, ); diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index 57d8612c076..f395faf906f 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -2462,6 +2462,94 @@ void main() { expect( router.routerDelegate.currentConfiguration.uri.toString(), '/other'); }); + + testWidgets('redirect when go to a shell route', + (WidgetTester tester) async { + final List routes = [ + ShellRoute( + redirect: (BuildContext context, GoRouterState state) => '/dummy', + builder: (BuildContext context, GoRouterState state, Widget child) => + Scaffold(appBar: AppBar(), body: child), + routes: [ + GoRoute( + path: '/other', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ), + GoRoute( + path: '/other2', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ), + ], + ), + GoRoute( + path: '/dummy', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ), + ]; + + final GoRouter router = await createRouter(routes, tester); + + for (final String shellRoute in ['/other', '/other2']) { + router.go(shellRoute); + await tester.pump(); + expect( + router.routerDelegate.currentConfiguration.uri.toString(), + '/dummy', + ); + } + }); + + testWidgets('redirect when go to a stateful shell route', + (WidgetTester tester) async { + final List routes = [ + StatefulShellRoute.indexedStack( + redirect: (BuildContext context, GoRouterState state) => '/dummy', + builder: (BuildContext context, GoRouterState state, + StatefulNavigationShell navigationShell) { + return navigationShell; + }, + branches: [ + StatefulShellBranch( + routes: [ + GoRoute( + path: '/other', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ), + ], + ), + StatefulShellBranch( + routes: [ + GoRoute( + path: '/other2', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ), + ], + ), + ], + ), + GoRoute( + path: '/dummy', + builder: (BuildContext context, GoRouterState state) => + const DummyScreen(), + ), + ]; + + final GoRouter router = await createRouter(routes, tester); + + for (final String shellRoute in ['/other', '/other2']) { + router.go(shellRoute); + await tester.pump(); + expect( + router.routerDelegate.currentConfiguration.uri.toString(), + '/dummy', + ); + } + }); }); group('initial location', () { @@ -3462,6 +3550,53 @@ void main() { expect(find.text('Screen B'), findsOneWidget); }); + testWidgets('can complete leaf route', (WidgetTester tester) async { + Future? routeFuture; + final List routes = [ + GoRoute( + path: '/', + builder: (BuildContext context, GoRouterState state) { + return Scaffold( + body: TextButton( + onPressed: () async { + routeFuture = context.push('/a'); + }, + child: const Text('press'), + ), + ); + }, + ), + ShellRoute( + builder: (BuildContext context, GoRouterState state, Widget child) { + return Scaffold( + body: child, + ); + }, + routes: [ + GoRoute( + path: '/a', + builder: (BuildContext context, GoRouterState state) { + return const Scaffold( + body: Text('Screen A'), + ); + }, + ), + ], + ), + ]; + + final GoRouter router = await createRouter(routes, tester); + expect(find.text('press'), findsOneWidget); + + await tester.tap(find.text('press')); + await tester.pumpAndSettle(); + expect(find.text('Screen A'), findsOneWidget); + + router.pop(true); + final bool? result = await routeFuture; + expect(result, isTrue); + }); + testWidgets( 'Pops from the correct Navigator when the Android back button is pressed', (WidgetTester tester) async { @@ -3821,6 +3956,50 @@ void main() { expect(statefulWidgetKey.currentState?.counter, equals(0)); }); + testWidgets( + 'Navigates to correct nested navigation tree in StatefulShellRoute ' + 'and maintains path parameters', (WidgetTester tester) async { + StatefulNavigationShell? routeState; + + final List routes = [ + GoRoute( + path: '/:id', + builder: (_, __) => const Placeholder(), + routes: [ + StatefulShellRoute.indexedStack( + builder: (BuildContext context, GoRouterState state, + StatefulNavigationShell navigationShell) { + routeState = navigationShell; + return navigationShell; + }, + branches: [ + StatefulShellBranch(routes: [ + GoRoute( + path: 'a', + builder: (BuildContext context, GoRouterState state) => + Text('a id is ${state.pathParameters['id']}'), + ), + ]), + StatefulShellBranch(routes: [ + GoRoute( + path: 'b', + builder: (BuildContext context, GoRouterState state) => + Text('b id is ${state.pathParameters['id']}'), + ), + ]), + ], + ), + ]) + ]; + + await createRouter(routes, tester, initialLocation: '/123/a'); + expect(find.text('a id is 123'), findsOneWidget); + + routeState!.goBranch(1); + await tester.pumpAndSettle(); + expect(find.text('b id is 123'), findsOneWidget); + }); + testWidgets('Maintains state for nested StatefulShellRoute', (WidgetTester tester) async { final GlobalKey rootNavigatorKey = diff --git a/packages/go_router/test/logging_test.dart b/packages/go_router/test/logging_test.dart index 3ae24561751..d3251ee1b14 100644 --- a/packages/go_router/test/logging_test.dart +++ b/packages/go_router/test/logging_test.dart @@ -11,6 +11,13 @@ import 'package:go_router/src/logging.dart'; import 'package:logging/logging.dart'; void main() { + tearDown(() { + // Reset the logging state + hierarchicalLoggingEnabled = false; + + // Reset the developer log function. + testDeveloperLog = null; + }); test('setLogging does not clear listeners', () { final StreamSubscription subscription = logger.onRecord.listen( expectAsync1((LogRecord r) {}, count: 2), @@ -26,6 +33,7 @@ void main() { testWidgets( 'It should not log anything the if debugLogDiagnostics is false', (WidgetTester tester) async { + testDeveloperLog = expectAsync1((LogRecord data) {}, count: 0); final StreamSubscription subscription = Logger.root.onRecord.listen( expectAsync1((LogRecord data) {}, count: 0), @@ -43,8 +51,48 @@ void main() { ); testWidgets( - 'It should not log the known routes and the initial route if debugLogDiagnostics is true', + 'It should log the known routes and the initial route if debugLogDiagnostics is true', + (WidgetTester tester) async { + testDeveloperLog = expectAsync1( + (LogRecord data) {}, + count: 2, + reason: 'Go router should log the 2 events', + ); + final List logs = []; + Logger.root.onRecord.listen( + (LogRecord event) => logs.add(event.message), + ); + GoRouter( + debugLogDiagnostics: true, + routes: [ + GoRoute( + path: '/', + builder: (_, GoRouterState state) => const Text('home'), + ), + ], + ); + + expect( + logs, + const [ + 'Full paths for routes:\n└─/ (Text)\n', + 'setting initial location null' + ], + reason: 'Go router should have sent the 2 events to the logger', + ); + }, + ); + + testWidgets( + 'Go router should not log itself the known routes but send the events to the logger when hierarchicalLoggingEnabled is true', (WidgetTester tester) async { + testDeveloperLog = expectAsync1( + (LogRecord data) {}, + count: 0, + reason: 'Go router should log the events itself', + ); + hierarchicalLoggingEnabled = true; + final List logs = []; Logger.root.onRecord.listen( (LogRecord event) => logs.add(event.message), @@ -62,9 +110,10 @@ void main() { expect( logs, const [ - 'Full paths for routes:\n => /\n', + 'Full paths for routes:\n└─/ (Text)\n', 'setting initial location null' ], + reason: 'Go router should have sent the 2 events to the logger', ); }, ); diff --git a/packages/go_router/test/on_exit_test.dart b/packages/go_router/test/on_exit_test.dart index 044a6ff399b..8a7bd83ab64 100644 --- a/packages/go_router/test/on_exit_test.dart +++ b/packages/go_router/test/on_exit_test.dart @@ -25,7 +25,7 @@ void main() { path: '1', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: page1), - onExit: (BuildContext context) { + onExit: (BuildContext context, GoRouterState state) { return allow; }, ) @@ -61,7 +61,7 @@ void main() { path: '/1', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: page1), - onExit: (BuildContext context) { + onExit: (BuildContext context, GoRouterState state) { return allow; }, ) @@ -95,7 +95,7 @@ void main() { path: '1', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: page1), - onExit: (BuildContext context) async { + onExit: (BuildContext context, GoRouterState state) async { return allow.future; }, ) @@ -139,7 +139,7 @@ void main() { path: '/1', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: page1), - onExit: (BuildContext context) async { + onExit: (BuildContext context, GoRouterState state) async { return allow.future; }, ) @@ -176,7 +176,7 @@ void main() { path: '/', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: home), - onExit: (BuildContext context) { + onExit: (BuildContext context, GoRouterState state) { return allow; }, ), @@ -201,7 +201,7 @@ void main() { path: '/', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: home), - onExit: (BuildContext context) async { + onExit: (BuildContext context, GoRouterState state) async { return allow; }, ), @@ -227,7 +227,7 @@ void main() { path: '/', builder: (BuildContext context, GoRouterState state) => DummyScreen(key: home), - onExit: (BuildContext context) { + onExit: (BuildContext context, GoRouterState state) { return allow; }, ), @@ -243,4 +243,232 @@ void main() { allow = true; expect(await router.routerDelegate.popRoute(), false); }); + + testWidgets('It should provide the correct uri to the onExit callback', + (WidgetTester tester) async { + final UniqueKey home = UniqueKey(); + final UniqueKey page1 = UniqueKey(); + final UniqueKey page2 = UniqueKey(); + final UniqueKey page3 = UniqueKey(); + late final GoRouterState onExitState1; + late final GoRouterState onExitState2; + late final GoRouterState onExitState3; + final List routes = [ + GoRoute( + path: '/', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: home), + routes: [ + GoRoute( + path: '1', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page1), + onExit: (BuildContext context, GoRouterState state) { + onExitState1 = state; + return true; + }, + routes: [ + GoRoute( + path: '2', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page2), + onExit: (BuildContext context, GoRouterState state) { + onExitState2 = state; + return true; + }, + routes: [ + GoRoute( + path: '3', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page3), + onExit: (BuildContext context, GoRouterState state) { + onExitState3 = state; + return true; + }, + ) + ], + ) + ], + ) + ], + ), + ]; + + final GoRouter router = + await createRouter(routes, tester, initialLocation: '/1/2/3'); + expect(find.byKey(page3), findsOneWidget); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(page2), findsOneWidget); + + expect(onExitState3.uri.toString(), '/1/2/3'); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(page1), findsOneWidget); + expect(onExitState2.uri.toString(), '/1/2'); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(home), findsOneWidget); + expect(onExitState1.uri.toString(), '/1'); + }); + + testWidgets( + 'It should provide the correct path parameters to the onExit callback', + (WidgetTester tester) async { + final UniqueKey page0 = UniqueKey(); + final UniqueKey page1 = UniqueKey(); + final UniqueKey page2 = UniqueKey(); + final UniqueKey page3 = UniqueKey(); + late final GoRouterState onExitState1; + late final GoRouterState onExitState2; + late final GoRouterState onExitState3; + final List routes = [ + GoRoute( + path: '/route-0/:id0', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page0), + ), + GoRoute( + path: '/route-1/:id1', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page1), + onExit: (BuildContext context, GoRouterState state) { + onExitState1 = state; + return true; + }, + ), + GoRoute( + path: '/route-2/:id2', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page2), + onExit: (BuildContext context, GoRouterState state) { + onExitState2 = state; + return true; + }, + ), + GoRoute( + path: '/route-3/:id3', + builder: (BuildContext context, GoRouterState state) { + return DummyScreen(key: page3); + }, + onExit: (BuildContext context, GoRouterState state) { + onExitState3 = state; + return true; + }, + ), + ]; + + final GoRouter router = await createRouter( + routes, + tester, + initialLocation: '/route-0/0?param0=0', + ); + unawaited(router.push('/route-1/1?param1=1')); + unawaited(router.push('/route-2/2?param2=2')); + unawaited(router.push('/route-3/3?param3=3')); + + await tester.pumpAndSettle(); + expect(find.byKey(page3), findsOne); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(page2), findsOne); + expect(onExitState3.uri.toString(), '/route-3/3?param3=3'); + expect(onExitState3.pathParameters, const {'id3': '3'}); + expect(onExitState3.fullPath, '/route-3/:id3'); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(page1), findsOne); + expect(onExitState2.uri.toString(), '/route-2/2?param2=2'); + expect(onExitState2.pathParameters, const {'id2': '2'}); + expect(onExitState2.fullPath, '/route-2/:id2'); + + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(page0), findsOne); + expect(onExitState1.uri.toString(), '/route-1/1?param1=1'); + expect(onExitState1.pathParameters, const {'id1': '1'}); + expect(onExitState1.fullPath, '/route-1/:id1'); + }, + ); + + testWidgets( + 'It should provide the correct path parameters to the onExit callback during a go', + (WidgetTester tester) async { + final UniqueKey page0 = UniqueKey(); + final UniqueKey page1 = UniqueKey(); + final UniqueKey page2 = UniqueKey(); + final UniqueKey page3 = UniqueKey(); + late final GoRouterState onExitState0; + late final GoRouterState onExitState1; + late final GoRouterState onExitState2; + final List routes = [ + GoRoute( + path: '/route-0/:id0', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page0), + onExit: (BuildContext context, GoRouterState state) { + onExitState0 = state; + return true; + }, + ), + GoRoute( + path: '/route-1/:id1', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page1), + onExit: (BuildContext context, GoRouterState state) { + onExitState1 = state; + return true; + }, + ), + GoRoute( + path: '/route-2/:id2', + builder: (BuildContext context, GoRouterState state) => + DummyScreen(key: page2), + onExit: (BuildContext context, GoRouterState state) { + onExitState2 = state; + return true; + }, + ), + GoRoute( + path: '/route-3/:id3', + builder: (BuildContext context, GoRouterState state) { + return DummyScreen(key: page3); + }, + ), + ]; + + final GoRouter router = await createRouter( + routes, + tester, + initialLocation: '/route-0/0?param0=0', + ); + expect(find.byKey(page0), findsOne); + + router.go('/route-1/1?param1=1'); + await tester.pumpAndSettle(); + expect(find.byKey(page1), findsOne); + expect(onExitState0.uri.toString(), '/route-0/0?param0=0'); + expect(onExitState0.pathParameters, const {'id0': '0'}); + expect(onExitState0.fullPath, '/route-0/:id0'); + + router.go('/route-2/2?param2=2'); + await tester.pumpAndSettle(); + expect(find.byKey(page2), findsOne); + expect(onExitState1.uri.toString(), '/route-1/1?param1=1'); + expect(onExitState1.pathParameters, const {'id1': '1'}); + expect(onExitState1.fullPath, '/route-1/:id1'); + + router.go('/route-3/3?param3=3'); + await tester.pumpAndSettle(); + expect(find.byKey(page3), findsOne); + expect(onExitState2.uri.toString(), '/route-2/2?param2=2'); + expect(onExitState2.pathParameters, const {'id2': '2'}); + expect(onExitState2.fullPath, '/route-2/:id2'); + }, + ); } diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 1308d42635d..1da35a672b6 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -10,11 +10,20 @@ import 'package:go_router/go_router.dart'; class _GoRouteDataBuild extends GoRouteData { const _GoRouteDataBuild(); + @override Widget build(BuildContext context, GoRouterState state) => const SizedBox(key: Key('build')); } +class _ShellRouteDataRedirectPage extends ShellRouteData { + const _ShellRouteDataRedirectPage(); + + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + '/build-page'; +} + class _ShellRouteDataBuilder extends ShellRouteData { const _ShellRouteDataBuilder(); @@ -49,7 +58,9 @@ class _ShellRouteDataWithKey extends ShellRouteData { class _GoRouteDataBuildWithKey extends GoRouteData { const _GoRouteDataBuildWithKey(this.key); + final Key key; + @override Widget build(BuildContext context, GoRouterState state) => SizedBox(key: key); } @@ -71,6 +82,7 @@ final ShellRoute _shellRouteDataBuilder = ShellRouteData.$route( class _GoRouteDataBuildPage extends GoRouteData { const _GoRouteDataBuildPage(); + @override Page buildPage(BuildContext context, GoRouterState state) => const MaterialPage( @@ -95,6 +107,14 @@ class _ShellRouteDataPageBuilder extends ShellRouteData { ); } +class _StatefulShellRouteDataRedirectPage extends StatefulShellRouteData { + const _StatefulShellRouteDataRedirectPage(); + + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + '/build-page'; +} + final GoRoute _goRouteDataBuildPage = GoRouteData.$route( path: '/build-page', factory: (GoRouterState state) => const _GoRouteDataBuildPage(), @@ -110,6 +130,21 @@ final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route( ], ); +final ShellRoute _shellRouteDataRedirect = ShellRouteData.$route( + factory: (GoRouterState state) => const _ShellRouteDataPageBuilder(), + routes: [ + ShellRouteData.$route( + factory: (GoRouterState state) => const _ShellRouteDataRedirectPage(), + routes: [ + GoRouteData.$route( + path: '/child', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + ), + ], + ), + ], +); + class _StatefulShellRouteDataBuilder extends StatefulShellRouteData { const _StatefulShellRouteDataBuilder(); @@ -174,6 +209,7 @@ final StatefulShellRoute _statefulShellRouteDataPageBuilder = class _GoRouteDataRedirectPage extends GoRouteData { const _GoRouteDataRedirectPage(); + @override FutureOr redirect(BuildContext context, GoRouterState state) => '/build-page'; @@ -311,6 +347,23 @@ void main() { expect(find.byKey(const Key('page-builder')), findsOneWidget); }, ); + + testWidgets( + 'It should redirect using the overridden redirect method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/child', + routes: [ + _goRouteDataBuildPage, + _shellRouteDataRedirect, + ], + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsNothing); + expect(find.byKey(const Key('buildPage')), findsOneWidget); + }, + ); }); group('StatefulShellRouteData', () { @@ -381,6 +434,36 @@ void main() { }, ); + testWidgets( + 'It should redirect using the overridden StatefulShellRoute redirect method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/child', + routes: [ + _goRouteDataBuildPage, + StatefulShellRouteData.$route( + factory: (GoRouterState state) => + const _StatefulShellRouteDataRedirectPage(), + branches: [ + StatefulShellBranchData.$branch( + routes: [ + GoRouteData.$route( + path: '/child', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + ), + ], + ) + ], + ) + ], + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsNothing); + expect(find.byKey(const Key('buildPage')), findsOneWidget); + }, + ); + testWidgets( 'It should redirect using the overridden redirect method', (WidgetTester tester) async { diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index 096e927331c..3332a70e1b8 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,21 @@ +## 2.7.0 + +- Adds an example and a test with `onExit`. +- Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 2.6.2 + +* Fixes a bug in the example app when accessing `BuildContext`. + +## 2.6.1 + +* Fixes typo in `durationDecoderHelperName`. +* Updates development dependency to `dart_style-2.3.6` (compatible with `analyzer-6.5.0`). + +## 2.6.0 + +* Adds support for passing observers to the StatefulShellBranch for the nested Navigator. + ## 2.5.1 - Updates examples to use uri.path instead of uri.toString() for accessing the current location. diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index b67ca5f5aa8..d555649825a 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -188,6 +188,9 @@ class HomeScreen extends StatelessWidget { unawaited(FamilyCountRoute(familyData.length) .push(context) .then((int? value) { + if (!context.mounted) { + return; + } if (value != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/packages/go_router_builder/example/lib/on_exit_example.dart b/packages/go_router_builder/example/lib/on_exit_example.dart new file mode 100644 index 00000000000..b574c852c6e --- /dev/null +++ b/packages/go_router_builder/example/lib/on_exit_example.dart @@ -0,0 +1,96 @@ +// 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. + +// ignore_for_file: public_member_api_docs, unreachable_from_main + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +part 'on_exit_example.g.dart'; + +void main() => runApp(App()); + +class App extends StatelessWidget { + App({super.key}); + + @override + Widget build(BuildContext context) => MaterialApp.router( + routerConfig: _router, + title: _appTitle, + ); + + final GoRouter _router = GoRouter(routes: $appRoutes); +} + +@TypedGoRoute( + path: '/', + routes: >[ + TypedGoRoute(path: 'sub-route') + ], +) +class HomeRoute extends GoRouteData { + const HomeRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); +} + +class SubRoute extends GoRouteData { + const SubRoute(); + + @override + Future onExit(BuildContext context, GoRouterState state) async { + final bool? confirmed = await showDialog( + context: context, + builder: (_) => AlertDialog( + content: const Text('Are you sure to leave this page?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Confirm'), + ), + ], + ), + ); + return confirmed ?? false; + } + + @override + Widget build(BuildContext context, GoRouterState state) => const SubScreen(); +} + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text(_appTitle)), + body: Center( + child: ElevatedButton( + onPressed: () => const SubRoute().go(context), + child: const Text('Go to sub screen'), + ), + )); +} + +class SubScreen extends StatelessWidget { + const SubScreen({super.key}); + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text('$_appTitle Sub screen')), + body: Center( + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Go back'), + ), + ), + ); +} + +const String _appTitle = 'GoRouter Example: builder'; diff --git a/packages/go_router_builder/example/lib/on_exit_example.g.dart b/packages/go_router_builder/example/lib/on_exit_example.g.dart new file mode 100644 index 00000000000..8a99156d8bf --- /dev/null +++ b/packages/go_router_builder/example/lib/on_exit_example.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'on_exit_example.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $homeRoute, + ]; + +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + factory: $HomeRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'sub-route', + factory: $SubRouteExtension._fromState, + ), + ], + ); + +extension $HomeRouteExtension on HomeRoute { + static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +extension $SubRouteExtension on SubRoute { + static SubRoute _fromState(GoRouterState state) => const SubRoute(); + + String get location => GoRouteData.$location( + '/sub-route', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index c9a2bd938c7..bc02b13402e 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -3,13 +3,13 @@ description: go_router_builder examples publish_to: none environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: collection: ^1.15.0 flutter: sdk: flutter - go_router: ^10.0.0 + go_router: ^14.1.1 provider: 6.0.5 dev_dependencies: diff --git a/packages/go_router_builder/example/test/on_exit_example_test.dart b/packages/go_router_builder/example/test/on_exit_example_test.dart new file mode 100644 index 00000000000..e41adb0c618 --- /dev/null +++ b/packages/go_router_builder/example/test/on_exit_example_test.dart @@ -0,0 +1,46 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router_builder_example/on_exit_example.dart'; + +void main() { + testWidgets('It should trigger the on exit when leaving the route', + (WidgetTester tester) async { + await tester.pumpWidget(App()); + expect(find.byType(HomeScreen), findsOne); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Go to sub screen')); + await tester.pumpAndSettle(); + + expect(find.byType(SubScreen), findsOne); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Go back')); + await tester.pumpAndSettle(); + + expect( + find.widgetWithText(AlertDialog, 'Are you sure to leave this page?'), + findsOne, + ); + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + expect(find.byType(HomeScreen), findsNothing); + expect(find.byType(SubScreen), findsOne); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Go back')); + await tester.pumpAndSettle(); + + expect( + find.widgetWithText(AlertDialog, 'Are you sure to leave this page?'), + findsOne, + ); + await tester.tap(find.text('Confirm')); + await tester.pumpAndSettle(); + + expect(find.byType(HomeScreen), findsOne); + expect(find.byType(SubScreen), findsNothing); + }); +} diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 599ff124f15..0cfa7a3928c 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -146,6 +146,7 @@ class StatefulShellBranchConfig extends RouteBaseConfig { required this.navigatorKey, required super.routeDataClass, required super.parent, + required this.observers, this.restorationScopeId, this.initialLocation, }) : super._(); @@ -159,6 +160,9 @@ class StatefulShellBranchConfig extends RouteBaseConfig { /// The initial route. final String? initialLocation; + /// The navigator observers. + final String? observers; + @override Iterable classDeclarations() => []; @@ -168,7 +172,8 @@ class StatefulShellBranchConfig extends RouteBaseConfig { String get routeConstructorParameters => '${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}' '${restorationScopeId == null ? '' : 'restorationScopeId: $restorationScopeId,'}' - '${initialLocation == null ? '' : 'initialLocation: $initialLocation,'}'; + '${initialLocation == null ? '' : 'initialLocation: $initialLocation,'}' + '${observers == null ? '' : 'observers: $observers,'}'; @override String get routeDataClassName => 'StatefulShellBranchData'; @@ -521,6 +526,10 @@ abstract class RouteBaseConfig { classElement, parameterName: r'$initialLocation', ), + observers: _generateParameterGetterCode( + classElement, + parameterName: r'$observers', + ), ); case 'TypedGoRoute': final ConstantReader pathValue = reader.read('path'); diff --git a/packages/go_router_builder/lib/src/type_helpers.dart b/packages/go_router_builder/lib/src/type_helpers.dart index a22edc8310d..94ddcd0a6e7 100644 --- a/packages/go_router_builder/lib/src/type_helpers.dart +++ b/packages/go_router_builder/lib/src/type_helpers.dart @@ -16,7 +16,7 @@ const String convertMapValueHelperName = r'_$convertMapValue'; /// The name of the generated, private helper for converting [Duration] to /// [bool]. -const String durationDecoderHelperName = r'_$duractionConverter'; +const String durationDecoderHelperName = r'_$durationConverter'; /// The name of the generated, private helper for converting [String] to [Enum]. const String enumExtensionHelperName = r'_$fromName'; @@ -91,6 +91,7 @@ String enumMapName(InterfaceType type) => '_\$${type.element.name}EnumMap'; String _stateValueAccess(ParameterElement element, Set pathParameters) { if (element.isExtraField) { + // ignore: avoid_redundant_argument_values return 'extra as ${element.type.getDisplayString(withNullability: true)}'; } diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index b552204767a..0e6e10b6b3d 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,12 +2,13 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 2.5.1 +version: 2.7.0 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 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: analyzer: ">=5.2.0 <7.0.0" @@ -22,8 +23,10 @@ dependencies: dev_dependencies: build_test: ^2.1.7 - dart_style: 2.3.2 - go_router: ^10.0.0 + dart_style: 2.3.6 + flutter: + sdk: flutter + go_router: ^14.0.0 test: ^1.20.0 topics: diff --git a/packages/go_router_builder/test_inputs/statefull_shell_branch_with_observers_data.dart b/packages/go_router_builder/test_inputs/statefull_shell_branch_with_observers_data.dart new file mode 100644 index 00000000000..07fac002af8 --- /dev/null +++ b/packages/go_router_builder/test_inputs/statefull_shell_branch_with_observers_data.dart @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:go_router/go_router.dart'; + +@TypedStatefulShellBranch() +class StatefulShellBranchWithObserversData extends StatefulShellBranchData { + const StatefulShellBranchWithObserversData(); + + static const String $initialLocation = '/main/second-tab'; + static const List $observers = []; +} diff --git a/packages/go_router_builder/test_inputs/statefull_shell_branch_with_observers_data.dart.expect b/packages/go_router_builder/test_inputs/statefull_shell_branch_with_observers_data.dart.expect new file mode 100644 index 00000000000..d851a3f1ffb --- /dev/null +++ b/packages/go_router_builder/test_inputs/statefull_shell_branch_with_observers_data.dart.expect @@ -0,0 +1,5 @@ +RouteBase get $statefulShellBranchWithObserversData => + StatefulShellBranchData.$branch( + initialLocation: StatefulShellBranchWithObserversData.$initialLocation, + observers: StatefulShellBranchWithObserversData.$observers, + ); diff --git a/packages/google_maps_flutter/google_maps_flutter/AUTHORS b/packages/google_maps_flutter/google_maps_flutter/AUTHORS index 9f1b53ee266..4fc3ace39f0 100644 --- a/packages/google_maps_flutter/google_maps_flutter/AUTHORS +++ b/packages/google_maps_flutter/google_maps_flutter/AUTHORS @@ -65,3 +65,4 @@ Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> Taha Tesser +Joonas Kerttula diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index c409f37beeb..c92f63deb0e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.7.0 + +* Adds support for BitmapDescriptor classes `AssetMapBitmap` and `BytesMapBitmap`. +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. + ## 2.6.1 * Updates the minimum version of the iOS implementation package to a version diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md index 0a9dd2fce99..1f1f68a7344 100644 --- a/packages/google_maps_flutter/google_maps_flutter/README.md +++ b/packages/google_maps_flutter/google_maps_flutter/README.md @@ -12,10 +12,6 @@ A Flutter plugin that provides a [Google Maps](https://developers.google.com/map [web-support]: https://docs.flutter.dev/reference/supported-platforms -## Usage - -To use this plugin, add `google_maps_flutter` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). - ## Getting Started * Get an API key at . @@ -58,7 +54,7 @@ This means that app will only be available for users that run Android SDK 20 or #### Display Mode The Android implementation supports multiple -[platform view display modes](https://flutter.dev/docs/development/platform-integration/platform-views). +[platform view display modes](https://docs.flutter.dev/platform-integration/android/platform-views). For details, see [the Android README](https://pub.dev/packages/google_maps_flutter_android#display-mode). #### Cloud-based map styling diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/AndroidManifest.xml index 7d7d8ed4cda..3e9d1a8aac2 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/AndroidManifest.xml @@ -16,9 +16,6 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> - diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/settings.gradle b/packages/google_maps_flutter/google_maps_flutter/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/settings.gradle +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { 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 3d99369e33b..2ab5ffccccb 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 @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -10,6 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:integration_test/integration_test.dart'; +import 'resources/icon_image_base64.dart'; import 'shared.dart'; /// Integration Tests that only need a standard [GoogleMapController]. @@ -401,6 +403,112 @@ void runTests() { expect(iwVisibleStatus, false); }); + testWidgets('markerWithAssetMapBitmap', (WidgetTester tester) async { + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/red_square.png', + imagePixelRatio: 1.0, + )), + }; + await pumpMap( + tester, + GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + ); + }); + + testWidgets('markerWithAssetMapBitmapCreate', (WidgetTester tester) async { + final ImageConfiguration imageConfiguration = ImageConfiguration( + devicePixelRatio: tester.view.devicePixelRatio, + ); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: await AssetMapBitmap.create( + imageConfiguration, + 'assets/red_square.png', + )), + }; + await pumpMap( + tester, + GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + ); + }); + + testWidgets('markerWithBytesMapBitmap', (WidgetTester tester) async { + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: BytesMapBitmap( + bytes, + imagePixelRatio: tester.view.devicePixelRatio, + ), + ), + }; + await pumpMap( + tester, + GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + ); + }); + + testWidgets('markerWithLegacyAsset', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final ImageConfiguration imageConfiguration = ImageConfiguration( + devicePixelRatio: tester.view.devicePixelRatio, + size: const Size(100, 100), + ); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: await BitmapDescriptor.fromAssetImage( + imageConfiguration, + 'assets/red_square.png', + )), + }; + await pumpMap( + tester, + GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + ); + + await tester.pumpAndSettle(); + }); + + testWidgets('markerWithLegacyBytes', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: BitmapDescriptor.fromBytes( + bytes, + size: const Size(100, 100), + )), + }; + await pumpMap( + tester, + GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + ); + + await tester.pumpAndSettle(); + }); + testWidgets('testTakeSnapshot', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/resources/icon_image.png b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/resources/icon_image.png new file mode 100644 index 00000000000..920b93f74d7 Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/resources/icon_image.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/resources/icon_image_base64.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/resources/icon_image_base64.dart new file mode 100644 index 00000000000..1bfc791ca38 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/resources/icon_image_base64.dart @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// This constant holds the base64-encoded data of a 16x16 PNG image of the +/// Flutter logo. +/// +/// See `icon_image.png` source in the same directory. +/// +/// To create or update this image, follow these steps: +/// 1. Create or update a 16x16 PNG image. +/// 2. Convert the image to a base64 string using a script below. +/// 3. Replace the existing base64 string below with the new one. +/// +/// Example of converting an image to base64 in Dart: +/// ```dart +/// import 'dart:convert'; +/// import 'dart:io'; +/// +/// void main() async { +/// final bytes = await File('icon_image.png').readAsBytes(); +/// final base64String = base64Encode(bytes); +/// print(base64String); +/// } +/// ``` +const String iconImageBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAIRlWElmTU' + '0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIA' + 'AIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQ' + 'AAABCgAwAEAAAAAQAAABAAAAAAx28c8QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1M' + 'OmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIH' + 'g6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8v' + 'd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcm' + 'lwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFk' + 'b2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk' + '9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6' + 'eG1wbWV0YT4KTMInWQAAAplJREFUOBF1k01ME1EQx2fe7tIPoGgTE6AJgQQSPaiH9oAtkFbsgX' + 'jygFcT0XjSkxcTDxtPJh6MR28ePMHBBA8cNLSIony0oBhEMVETP058tE132+7uG3cW24DAXN57' + '2fn9/zPz3iIcEdEl0nIxtNLr1IlVeoMadkubKmoL+u2SzAV8IjV5Ekt4GN+A8+VOUPwLarOI2G' + 'Vpqq0i4JQorwQxPtWHVZ1IKP8LNGDXGaSyqARFxDGo7MJBy4XVf3AyQ+qTHnTEXoF9cFUy3OkY' + '0oWxmWFtD5xNoc1sQ6AOn1+hCNTkkhKow8KFZV77tVs2O9dhFvBm0IA/U0RhZ7/ocEx23oUDlh' + 'h8HkNjZIN8Lb3gOU8gOp7AKJHCB2/aNZkTftHumNzzbtl2CBPZHqxw8mHhVZBeoz6w5DvhE2FZ' + 'lQYPjKdd2/qRyKZ6KsPv7TEk7EYEk0A0EUmJduHRy1i4oLKqgmC59ZggAdwrC9pFuWy1iUT2rA' + 'uv0h2UdNtNqxCBBkgqorjOMOgksN7CxQ90vEb00U3c3LIwyo9o8FXxQVNr8Coqyk+S5EPBXnjt' + 'xRmc4TegI7qWbvBkeeUbGMnTCd4nZnYeDOWIEtlC6cKK/JJepY3hZSvN33jovO6L0XFqPKqBTO' + 'FuapUoPr1lxDM7cmC2TAOz25cYSGa++feBew/cjpc0V+mNT29/HZp3KDFTNLvuTRPEHy5065lj' + 'Xn4y41XM+wP/AlcycRmdc3MUhvLm/J/ceu/3qUVT62oP2EZpjSylHybHSpDUVcjq9gEBVo0+Xt' + 'JyN2IWRO+3QUforRoKnZLVsglaMECW+YmMSj9M3SrC6Lg71CMiqWfUrJ6ywzefhnZ+G69BaKdB' + 'WhXQAn6wzDUpfUPw7MrmX/WhbfmKblw+AAAAAElFTkSuQmCC'; diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/custom_marker_icon.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/custom_marker_icon.dart new file mode 100644 index 00000000000..8940762f02e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/custom_marker_icon.dart @@ -0,0 +1,56 @@ +// 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:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +/// Returns a generated png image in [ByteData] format with the requested size. +Future createCustomMarkerIconImage({required Size size}) async { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final _MarkerPainter painter = _MarkerPainter(); + + painter.paint(canvas, size); + + final ui.Image image = await recorder + .endRecording() + .toImage(size.width.floor(), size.height.floor()); + + final ByteData? bytes = + await image.toByteData(format: ui.ImageByteFormat.png); + return bytes!; +} + +class _MarkerPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + const RadialGradient gradient = RadialGradient( + colors: [Colors.yellow, Colors.red], + stops: [0.4, 1.0], + ); + + // Draw radial gradient + canvas.drawRect( + rect, + Paint()..shader = gradient.createShader(rect), + ); + + // Draw diagonal black line + canvas.drawLine( + Offset.zero, + Offset(size.width, size.height), + Paint() + ..color = Colors.black + ..strokeWidth = 1, + ); + } + + @override + bool shouldRepaint(_MarkerPainter oldDelegate) => false; + @override + bool shouldRebuildSemantics(_MarkerPainter oldDelegate) => false; +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/marker_icons.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/marker_icons.dart index 847e5ae3678..d2010b65b13 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/marker_icons.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/marker_icons.dart @@ -5,9 +5,13 @@ // ignore_for_file: public_member_api_docs // ignore_for_file: unawaited_futures +import 'dart:async'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'custom_marker_icon.dart'; import 'page.dart'; class MarkerIconsPage extends GoogleMapExampleAppPage { @@ -29,66 +33,303 @@ class MarkerIconsBody extends StatefulWidget { const LatLng _kMapCenter = LatLng(52.4478, -3.5402); +enum _MarkerSizeOption { + original, + width30, + height40, + size30x60, + size120x60, +} + class MarkerIconsBodyState extends State { + final Size _markerAssetImageSize = const Size(48, 48); + _MarkerSizeOption _currentSizeOption = _MarkerSizeOption.original; + Set _markers = {}; + bool _scalingEnabled = true; + bool _mipMapsEnabled = true; GoogleMapController? controller; - BitmapDescriptor? _markerIcon; + AssetMapBitmap? _markerIconAsset; + BytesMapBitmap? _markerIconBytes; + final int _markersAmountPerType = 15; + bool get _customSizeEnabled => + _currentSizeOption != _MarkerSizeOption.original; @override Widget build(BuildContext context) { - _createMarkerImageFromAsset(context); + _createCustomMarkerIconImages(context); + final Size referenceSize = _getMarkerReferenceSize(); return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Center( - child: SizedBox( - width: 350.0, - height: 300.0, - child: GoogleMap( - initialCameraPosition: const CameraPosition( - target: _kMapCenter, - zoom: 7.0, + Column(children: [ + Center( + child: SizedBox( + width: 350.0, + height: 300.0, + child: GoogleMap( + initialCameraPosition: const CameraPosition( + target: _kMapCenter, + zoom: 7.0, + ), + markers: _markers, + onMapCreated: _onMapCreated, + ), + ), + ), + TextButton( + onPressed: () => _toggleScaling(context), + child: Text(_scalingEnabled + ? 'Disable auto scaling' + : 'Enable auto scaling'), + ), + if (_scalingEnabled) ...[ + Container( + width: referenceSize.width, + height: referenceSize.height, + decoration: BoxDecoration( + border: Border.all(), ), - markers: {_createMarker()}, - onMapCreated: _onMapCreated, ), + Text( + 'Reference box with size of ${referenceSize.width} x ${referenceSize.height} in logical pixels.'), + const SizedBox(height: 10), + Image.asset( + 'assets/red_square.png', + scale: _mipMapsEnabled ? null : 1.0, + ), + const Text('Asset image rendered with flutter'), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Marker size:'), + const SizedBox(width: 10), + DropdownButton<_MarkerSizeOption>( + value: _currentSizeOption, + onChanged: (_MarkerSizeOption? newValue) { + if (newValue != null) { + setState(() { + _currentSizeOption = newValue; + _updateMarkerImages(context); + }); + } + }, + items: + _MarkerSizeOption.values.map((_MarkerSizeOption option) { + return DropdownMenuItem<_MarkerSizeOption>( + value: option, + child: Text(_getMarkerSizeOptionName(option)), + ); + }).toList(), + ) + ], + ), + ], + TextButton( + onPressed: () => _toggleMipMaps(context), + child: Text(_mipMapsEnabled ? 'Disable mipmaps' : 'Enable mipmaps'), ), - ) + ]) ], ); } - Marker _createMarker() { - if (_markerIcon != null) { - return Marker( - markerId: const MarkerId('marker_1'), - position: _kMapCenter, - icon: _markerIcon!, - ); + String _getMarkerSizeOptionName(_MarkerSizeOption option) { + switch (option) { + case _MarkerSizeOption.original: + return 'Original'; + case _MarkerSizeOption.width30: + return 'Width 30'; + case _MarkerSizeOption.height40: + return 'Height 40'; + case _MarkerSizeOption.size30x60: + return '30x60'; + case _MarkerSizeOption.size120x60: + return '120x60'; + } + } + + (double? width, double? height) _getCurrentMarkerSize() { + if (_scalingEnabled) { + switch (_currentSizeOption) { + case _MarkerSizeOption.width30: + return (30, null); + case _MarkerSizeOption.height40: + return (null, 40); + case _MarkerSizeOption.size30x60: + return (30, 60); + case _MarkerSizeOption.size120x60: + return (120, 60); + case _MarkerSizeOption.original: + return (_markerAssetImageSize.width, _markerAssetImageSize.height); + } } else { - return const Marker( - markerId: MarkerId('marker_1'), - position: _kMapCenter, - ); + return (_markerAssetImageSize.width, _markerAssetImageSize.height); } } - Future _createMarkerImageFromAsset(BuildContext context) async { - if (_markerIcon == null) { - final ImageConfiguration imageConfiguration = - createLocalImageConfiguration(context, size: const Size.square(48)); - BitmapDescriptor.fromAssetImage( - imageConfiguration, 'assets/red_square.png') - .then(_updateBitmap); + // Helper method to calculate reference size for custom marker size. + Size _getMarkerReferenceSize() { + final (double? width, double? height) = _getCurrentMarkerSize(); + + // Calculates reference size using _markerAssetImageSize aspect ration: + + if (width != null && height != null) { + return Size(width, height); + } else if (width != null) { + return Size(width, + width * _markerAssetImageSize.height / _markerAssetImageSize.width); + } else if (height != null) { + return Size( + height * _markerAssetImageSize.width / _markerAssetImageSize.height, + height); + } else { + return _markerAssetImageSize; } } - void _updateBitmap(BitmapDescriptor bitmap) { + void _toggleMipMaps(BuildContext context) { + _mipMapsEnabled = !_mipMapsEnabled; + _updateMarkerImages(context); + } + + void _toggleScaling(BuildContext context) { + _scalingEnabled = !_scalingEnabled; + _updateMarkerImages(context); + } + + void _updateMarkerImages(BuildContext context) { + _updateMarkerAssetImage(context); + _updateMarkerBytesImage(context); + _updateMarkers(); + } + + Marker _createAssetMarker(int index) { + final LatLng position = + LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude - 1); + + return Marker( + markerId: MarkerId('marker_asset_$index'), + position: position, + icon: _markerIconAsset!, + ); + } + + Marker _createBytesMarker(int index) { + final LatLng position = + LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude + 1); + + return Marker( + markerId: MarkerId('marker_bytes_$index'), + position: position, + icon: _markerIconBytes!, + ); + } + + void _updateMarkers() { + final Set markers = {}; + for (int i = 0; i < _markersAmountPerType; i++) { + if (_markerIconAsset != null) { + markers.add(_createAssetMarker(i)); + } + if (_markerIconBytes != null) { + markers.add(_createBytesMarker(i)); + } + } setState(() { - _markerIcon = bitmap; + _markers = markers; }); } + Future _updateMarkerAssetImage(BuildContext context) async { + // Width and height are used only for custom size. + final (double? width, double? height) = + _scalingEnabled && _customSizeEnabled + ? _getCurrentMarkerSize() + : (null, null); + + AssetMapBitmap assetMapBitmap; + if (_mipMapsEnabled) { + final ImageConfiguration imageConfiguration = + createLocalImageConfiguration( + context, + ); + + assetMapBitmap = await AssetMapBitmap.create( + imageConfiguration, + 'assets/red_square.png', + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none, + ); + } else { + // Uses hardcoded asset path + // This bypasses the asset resolving logic and allows to load the asset + // with precise path. + assetMapBitmap = AssetMapBitmap( + 'assets/red_square.png', + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none, + ); + } + + _updateAssetBitmap(assetMapBitmap); + } + + Future _updateMarkerBytesImage(BuildContext context) async { + final double? devicePixelRatio = + MediaQuery.maybeDevicePixelRatioOf(context); + + final Size bitmapLogicalSize = _getMarkerReferenceSize(); + final double? imagePixelRatio = _scalingEnabled ? devicePixelRatio : null; + + // Create canvasSize with physical marker size + final Size canvasSize = Size( + bitmapLogicalSize.width * (imagePixelRatio ?? 1.0), + bitmapLogicalSize.height * (imagePixelRatio ?? 1.0)); + + final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize); + + // Width and height are used only for custom size. + final (double? width, double? height) = + _scalingEnabled && _customSizeEnabled + ? _getCurrentMarkerSize() + : (null, null); + + final BytesMapBitmap bitmap = BytesMapBitmap(bytes.buffer.asUint8List(), + imagePixelRatio: imagePixelRatio, + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none); + + _updateBytesBitmap(bitmap); + } + + void _updateAssetBitmap(AssetMapBitmap bitmap) { + _markerIconAsset = bitmap; + _updateMarkers(); + } + + void _updateBytesBitmap(BytesMapBitmap bitmap) { + _markerIconBytes = bitmap; + _updateMarkers(); + } + + void _createCustomMarkerIconImages(BuildContext context) { + if (_markerIconAsset == null) { + _updateMarkerAssetImage(context); + } + + if (_markerIconBytes == null) { + _updateMarkerBytesImage(context); + } + } + void _onMapCreated(GoogleMapController controllerParam) { setState(() { controller = controllerParam; diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart index dc3a087058c..1534f3b64ba 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart @@ -7,11 +7,11 @@ import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'custom_marker_icon.dart'; import 'page.dart'; class PlaceMarkerPage extends GoogleMapExampleAppPage { @@ -266,26 +266,10 @@ class PlaceMarkerBodyState extends State { }); } - Future _getAssetIcon(BuildContext context) async { - final Completer bitmapIcon = - Completer(); - final ImageConfiguration config = createLocalImageConfiguration(context); - - const AssetImage('assets/red_square.png') - .resolve(config) - .addListener(ImageStreamListener((ImageInfo image, bool sync) async { - final ByteData? bytes = - await image.image.toByteData(format: ImageByteFormat.png); - if (bytes == null) { - bitmapIcon.completeError(Exception('Unable to encode icon')); - return; - } - final BitmapDescriptor bitmap = - BitmapDescriptor.fromBytes(bytes.buffer.asUint8List()); - bitmapIcon.complete(bitmap); - })); - - return bitmapIcon.future; + Future _getMarkerIcon(BuildContext context) async { + const Size canvasSize = Size(48, 48); + final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize); + return BytesMapBitmap(bytes.buffer.asUint8List()); } @override @@ -382,7 +366,7 @@ class PlaceMarkerBodyState extends State { onPressed: selectedId == null ? null : () { - _getAssetIcon(context).then( + _getMarkerIcon(context).then( (BitmapDescriptor icon) { _setMarkerIcon(selectedId, icon); }, diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polyline.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polyline.dart index e7997fa4445..7cb07ae37db 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polyline.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polyline.dart @@ -291,10 +291,10 @@ class PlacePolylineBodyState extends State { child: const Text('change joint type [Android only]'), ), TextButton( - onPressed: isIOS || (selectedId == null) + onPressed: (selectedId == null) ? null : () => _changePattern(selectedId), - child: const Text('change pattern [Android only]'), + child: const Text('change pattern'), ), ], ) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index ba306fd8bd9..4911beb6579 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the google_maps_flutter plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: cupertino_icons: ^1.0.5 @@ -18,8 +18,8 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - google_maps_flutter_android: ^2.5.0 - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_android: ^2.9.0 + google_maps_flutter_platform_interface: ^2.7.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart index 7dbcee8e92c..7eb4af94731 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart @@ -17,7 +17,9 @@ export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf show ArgumentCallback, ArgumentCallbacks, + AssetMapBitmap, BitmapDescriptor, + BytesMapBitmap, CameraPosition, CameraPositionCallback, CameraTargetBounds, @@ -29,6 +31,7 @@ export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf JointType, LatLng, LatLngBounds, + MapBitmapScaling, MapStyleException, MapType, Marker, diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index c50ffccfa63..bbd3a425248 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -52,7 +52,7 @@ class AndroidGoogleMapsFlutter { /// This implementation uses hybrid composition to render the Google Maps /// Widget on Android. This comes at the cost of some performance on Android /// versions below 10. See - /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more + /// https://docs.flutter.dev/platform-integration/android/platform-views#performance for more /// information. @Deprecated( 'See https://pub.dev/packages/google_maps_flutter_android#display-mode') @@ -70,7 +70,7 @@ class AndroidGoogleMapsFlutter { /// This implementation uses hybrid composition to render the Google Maps /// Widget on Android. This comes at the cost of some performance on Android /// versions below 10. See - /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more + /// https://docs.flutter.dev/platform-integration/android/platform-views#performance for more /// information. @Deprecated( 'See https://pub.dev/packages/google_maps_flutter_android#display-mode') diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 5061448ac3e..3ab5430b095 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.6.1 +version: 2.7.0 environment: - sdk: ^3.3.0 - flutter: ">=3.19.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: @@ -21,10 +21,10 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_android: ^2.7.0 - google_maps_flutter_ios: ^2.5.0 - google_maps_flutter_platform_interface: ^2.6.0 - google_maps_flutter_web: ^0.5.6 + google_maps_flutter_android: ^2.9.0 + google_maps_flutter_ios: ^2.7.0 + google_maps_flutter_platform_interface: ^2.7.0 + google_maps_flutter_web: ^0.5.8 dev_dependencies: flutter_test: diff --git a/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS index 9f1b53ee266..4fc3ace39f0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS +++ b/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS @@ -65,3 +65,4 @@ Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> Taha Tesser +Joonas Kerttula 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 a099bb5ef42..7a1833338bf 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,28 @@ +## 2.11.0 + +* Converts additional platform calls to Pigeon. + +## 2.10.0 + +* Converts some platform calls to Pigeon. + +## 2.9.1 + +* Converts inspector interface platform calls to Pigeon. + +## 2.9.0 + +* Adds support for BitmapDescriptor classes `AssetMapBitmap` and `BytesMapBitmap`. + +## 2.8.1 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 2.8.0 + +* Adds support for marker clustering. + ## 2.7.0 * Adds support for `MapConfiguration.style`. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/README.md b/packages/google_maps_flutter/google_maps_flutter_android/README.md index 8c482864dfd..ccef3a0b127 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/README.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/README.md @@ -79,7 +79,7 @@ WARNING: `AndroidMapRenderer.legacy` is known to crash apps and is no longer sup and therefore cannot be supported by the Flutter team. [1]: https://pub.dev/packages/google_maps_flutter -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://docs.flutter.dev/development/platform-integration/android/platform-views [4]: https://github.com/flutter/flutter/issues/103686 [5]: https://developers.google.com/maps/documentation/android-sdk/renderer 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 e9ea102e397..00781bb7718 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 @@ -41,6 +41,7 @@ android { dependencies { implementation "androidx.annotation:annotation:1.7.0" implementation 'com.google.android.gms:play-services-maps:18.2.0' + implementation 'com.google.maps.android:android-maps-utils:3.6.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/main/java/io/flutter/plugins/googlemaps/CirclesController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java index d128d9544b1..2b52641caa0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java @@ -4,6 +4,7 @@ package io.flutter.plugins.googlemaps; +import androidx.annotation.NonNull; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.CircleOptions; @@ -31,31 +32,28 @@ void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } - void addCircles(List circlesToAdd) { + void addJsonCircles(List circlesToAdd) { if (circlesToAdd != null) { for (Object circleToAdd : circlesToAdd) { - addCircle(circleToAdd); + addJsonCircle(circleToAdd); } } } - void changeCircles(List circlesToChange) { - if (circlesToChange != null) { - for (Object circleToChange : circlesToChange) { - changeCircle(circleToChange); - } + void addCircles(@NonNull List circlesToAdd) { + for (Messages.PlatformCircle circleToAdd : circlesToAdd) { + addJsonCircle(circleToAdd.getJson()); } } - void removeCircles(List circleIdsToRemove) { - if (circleIdsToRemove == null) { - return; + void changeCircles(@NonNull List circlesToChange) { + for (Object circleToChange : circlesToChange) { + changeCircle(circleToChange); } - for (Object rawCircleId : circleIdsToRemove) { - if (rawCircleId == null) { - continue; - } - String circleId = (String) rawCircleId; + } + + void removeCircles(@NonNull List circleIdsToRemove) { + for (String circleId : circleIdsToRemove) { final CircleController circleController = circleIdToController.remove(circleId); if (circleController != null) { circleController.remove(); @@ -77,7 +75,7 @@ boolean onCircleTap(String googleCircleId) { return false; } - private void addCircle(Object circle) { + private void addJsonCircle(Object circle) { if (circle == null) { return; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/ClusterManagersController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/ClusterManagersController.java new file mode 100644 index 00000000000..16666ac0dc3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/ClusterManagersController.java @@ -0,0 +1,242 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.ClusterItem; +import com.google.maps.android.clustering.ClusterManager; +import com.google.maps.android.clustering.view.DefaultClusterRenderer; +import com.google.maps.android.collections.MarkerManager; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Controls cluster managers and exposes interfaces for adding and removing cluster items for + * specific cluster managers. + */ +class ClusterManagersController + implements GoogleMap.OnCameraIdleListener, + ClusterManager.OnClusterClickListener { + @NonNull private final Context context; + @NonNull private final HashMap> clusterManagerIdToManager; + @NonNull private final MethodChannel methodChannel; + @Nullable private MarkerManager markerManager; + @Nullable private GoogleMap googleMap; + + @Nullable + private ClusterManager.OnClusterItemClickListener clusterItemClickListener; + + @Nullable + private ClusterManagersController.OnClusterItemRendered + clusterItemRenderedListener; + + ClusterManagersController(MethodChannel methodChannel, Context context) { + this.clusterManagerIdToManager = new HashMap<>(); + this.context = context; + this.methodChannel = methodChannel; + } + + void init(GoogleMap googleMap, MarkerManager markerManager) { + this.markerManager = markerManager; + this.googleMap = googleMap; + } + + void setClusterItemClickListener( + @Nullable ClusterManager.OnClusterItemClickListener listener) { + clusterItemClickListener = listener; + initListenersForClusterManagers(); + } + + void setClusterItemRenderedListener( + @Nullable ClusterManagersController.OnClusterItemRendered listener) { + clusterItemRenderedListener = listener; + } + + private void initListenersForClusterManagers() { + for (Map.Entry> entry : + clusterManagerIdToManager.entrySet()) { + initListenersForClusterManager(entry.getValue(), this, clusterItemClickListener); + } + } + + private void initListenersForClusterManager( + ClusterManager clusterManager, + @Nullable ClusterManager.OnClusterClickListener clusterClickListener, + @Nullable ClusterManager.OnClusterItemClickListener clusterItemClickListener) { + clusterManager.setOnClusterClickListener(clusterClickListener); + clusterManager.setOnClusterItemClickListener(clusterItemClickListener); + } + + /** Adds new ClusterManagers to the controller. */ + void addJsonClusterManagers(@NonNull List clusterManagersToAdd) { + for (Object clusterToAdd : clusterManagersToAdd) { + String clusterManagerId = getClusterManagerId(clusterToAdd); + if (clusterManagerId == null) { + throw new IllegalArgumentException("clusterManagerId was null"); + } + addClusterManager(clusterManagerId); + } + } + + /** Adds new ClusterManagers to the controller. */ + void addClusterManagers(@NonNull List clusterManagersToAdd) { + for (Messages.PlatformClusterManager clusterToAdd : clusterManagersToAdd) { + addClusterManager(clusterToAdd.getIdentifier()); + } + } + + /** Adds new ClusterManager to the controller. */ + void addClusterManager(String clusterManagerId) { + ClusterManager clusterManager = + new ClusterManager(context, googleMap, markerManager); + ClusterRenderer clusterRenderer = + new ClusterRenderer(context, googleMap, clusterManager, this); + clusterManager.setRenderer(clusterRenderer); + initListenersForClusterManager(clusterManager, this, clusterItemClickListener); + clusterManagerIdToManager.put(clusterManagerId, clusterManager); + } + + /** Removes ClusterManagers by given cluster manager IDs from the controller. */ + public void removeClusterManagers(@NonNull List clusterManagerIdsToRemove) { + for (String clusterManagerId : clusterManagerIdsToRemove) { + removeClusterManager(clusterManagerId); + } + } + + /** + * Removes the ClusterManagers by the given cluster manager ID from the controller. The reference + * to this cluster manager is removed from the clusterManagerIdToManager and it will be garbage + * collected later. + */ + private void removeClusterManager(Object clusterManagerId) { + // Remove the cluster manager from the hash map to allow it to be garbage collected. + final ClusterManager clusterManager = + clusterManagerIdToManager.remove(clusterManagerId); + if (clusterManager == null) { + return; + } + initListenersForClusterManager(clusterManager, null, null); + clusterManager.clearItems(); + clusterManager.cluster(); + } + + /** Adds item to the ClusterManager it belongs to. */ + public void addItem(MarkerBuilder item) { + ClusterManager clusterManager = + clusterManagerIdToManager.get(item.clusterManagerId()); + if (clusterManager != null) { + clusterManager.addItem(item); + clusterManager.cluster(); + } + } + + /** Removes item from the ClusterManager it belongs to. */ + public void removeItem(MarkerBuilder item) { + ClusterManager clusterManager = + clusterManagerIdToManager.get(item.clusterManagerId()); + if (clusterManager != null) { + clusterManager.removeItem(item); + clusterManager.cluster(); + } + } + + /** Called when ClusterRenderer has rendered new visible marker to the map. */ + void onClusterItemRendered(@NonNull MarkerBuilder item, @NonNull Marker marker) { + // If map is being disposed, clusterItemRenderedListener might have been cleared and + // set to null. + if (clusterItemRenderedListener != null) { + clusterItemRenderedListener.onClusterItemRendered(item, marker); + } + } + + /** Reads clusterManagerId from object data. */ + @SuppressWarnings("unchecked") + private static String getClusterManagerId(Object clusterManagerData) { + Map clusterMap = (Map) clusterManagerData; + // Ref: google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart ClusterManager.toJson() method. + return (String) clusterMap.get("clusterManagerId"); + } + + /** + * Requests all current clusters from the algorithm of the requested ClusterManager and converts + * them to result response. + */ + public @NonNull Set> getClustersWithClusterManagerId( + String clusterManagerId) { + ClusterManager clusterManager = clusterManagerIdToManager.get(clusterManagerId); + if (clusterManager == null) { + throw new Messages.FlutterError( + "Invalid clusterManagerId", + "getClusters called with invalid clusterManagerId:" + clusterManagerId, + null); + } + return clusterManager.getAlgorithm().getClusters(googleMap.getCameraPosition().zoom); + } + + @Override + public void onCameraIdle() { + for (Map.Entry> entry : + clusterManagerIdToManager.entrySet()) { + entry.getValue().onCameraIdle(); + } + } + + @Override + public boolean onClusterClick(Cluster cluster) { + if (cluster.getSize() > 0) { + MarkerBuilder[] builders = cluster.getItems().toArray(new MarkerBuilder[0]); + String clusterManagerId = builders[0].clusterManagerId(); + methodChannel.invokeMethod("cluster#onTap", Convert.clusterToJson(clusterManagerId, cluster)); + } + + // Return false to allow the default behavior of the cluster click event to occur. + return false; + } + + /** + * ClusterRenderer builds marker options for new markers to be rendered to the map. After cluster + * item (marker) is rendered, it is sent to the listeners for control. + */ + private static class ClusterRenderer extends DefaultClusterRenderer { + private final ClusterManagersController clusterManagersController; + + public ClusterRenderer( + Context context, + GoogleMap map, + ClusterManager clusterManager, + ClusterManagersController clusterManagersController) { + super(context, map, clusterManager); + this.clusterManagersController = clusterManagersController; + } + + @Override + protected void onBeforeClusterItemRendered( + @NonNull T item, @NonNull MarkerOptions markerOptions) { + // Builds new markerOptions for new marker created by the ClusterRenderer under + // ClusterManager. + item.update(markerOptions); + } + + @Override + protected void onClusterItemRendered(@NonNull T item, @NonNull Marker marker) { + super.onClusterItemRendered(item, marker); + clusterManagersController.onClusterItemRendered(item, marker); + } + } + + /** Interface for handling situations where clusterManager adds new visible marker to the map. */ + public interface OnClusterItemRendered { + void onClusterItemRendered(@NonNull T item, @NonNull Marker marker); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index b473f1ea17d..71455b668e8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -4,12 +4,16 @@ package io.flutter.plugins.googlemaps; +import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.ButtCap; @@ -25,6 +29,10 @@ import com.google.android.gms.maps.model.RoundCap; import com.google.android.gms.maps.model.SquareCap; import com.google.android.gms.maps.model.Tile; +import com.google.maps.android.clustering.Cluster; +import io.flutter.FlutterInjector; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -34,43 +42,67 @@ /** Conversions between JSON-like values and GoogleMaps data types. */ class Convert { - // TODO(hamdikahloun): FlutterMain has been deprecated and should be replaced with FlutterLoader - // when it's available in Stable channel: https://github.com/flutter/flutter/issues/70923. - @SuppressWarnings("deprecation") - private static BitmapDescriptor toBitmapDescriptor(Object o) { + private static BitmapDescriptor toBitmapDescriptor( + Object o, AssetManager assetManager, float density) { final List data = toList(o); - switch (toString(data.get(0))) { + final String descriptorType = toString(data.get(0)); + switch (descriptorType) { case "defaultMarker": if (data.size() == 1) { return BitmapDescriptorFactory.defaultMarker(); } else { - return BitmapDescriptorFactory.defaultMarker(toFloat(data.get(1))); + final float hue = toFloat(data.get(1)); + return BitmapDescriptorFactory.defaultMarker(hue); } case "fromAsset": + final String assetPath = toString(data.get(1)); if (data.size() == 2) { return BitmapDescriptorFactory.fromAsset( - io.flutter.view.FlutterMain.getLookupKeyForAsset(toString(data.get(1)))); + FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(assetPath)); } else { + final String assetPackage = toString(data.get(2)); return BitmapDescriptorFactory.fromAsset( - io.flutter.view.FlutterMain.getLookupKeyForAsset( - toString(data.get(1)), toString(data.get(2)))); + FlutterInjector.instance() + .flutterLoader() + .getLookupKeyForAsset(assetPath, assetPackage)); } case "fromAssetImage": + final String assetImagePath = toString(data.get(1)); if (data.size() == 3) { return BitmapDescriptorFactory.fromAsset( - io.flutter.view.FlutterMain.getLookupKeyForAsset(toString(data.get(1)))); + FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(assetImagePath)); } else { throw new IllegalArgumentException( "'fromAssetImage' Expected exactly 3 arguments, got: " + data.size()); } case "fromBytes": - return getBitmapFromBytes(data); + return getBitmapFromBytesLegacy(data); + case "asset": + if (!(data.get(1) instanceof Map)) { + throw new IllegalArgumentException("'asset' expected a map as the second parameter"); + } + final Map assetData = toMap(data.get(1)); + return getBitmapFromAsset( + assetData, + assetManager, + density, + new BitmapDescriptorFactoryWrapper(), + new FlutterInjectorWrapper()); + case "bytes": + if (!(data.get(1) instanceof Map)) { + throw new IllegalArgumentException("'bytes' expected a map as the second parameter"); + } + final Map byteData = toMap(data.get(1)); + return getBitmapFromBytes(byteData, density, new BitmapDescriptorFactoryWrapper()); default: throw new IllegalArgumentException("Cannot interpret " + o + " as BitmapDescriptor"); } } - private static BitmapDescriptor getBitmapFromBytes(List data) { + // Used for deprecated fromBytes bitmap descriptor. + // Can be removed after support for "fromBytes" bitmap descriptor type is + // removed. + private static BitmapDescriptor getBitmapFromBytesLegacy(List data) { if (data.size() == 2) { try { Bitmap bitmap = toBitmap(data.get(1)); @@ -85,6 +117,185 @@ private static BitmapDescriptor getBitmapFromBytes(List data) { } } + /** + * Creates a BitmapDescriptor object from bytes data. + * + *

This method requires the `byteData` map to contain specific keys: 'byteData' for image + * bytes, 'bitmapScaling' for scaling mode, and 'imagePixelRatio' for scale ratio. It may + * optionally include 'width' and/or 'height' for explicit image dimensions. + * + * @param byteData a map containing the byte data and scaling instructions. Expected keys are: + * 'byteData': the actual bytes of the image, 'bitmapScaling': the scaling mode, either 'auto' + * or 'none', 'imagePixelRatio': used with 'auto' bitmapScaling if width or height are not + * provided, 'width' (optional): the desired width, which affects scaling if 'height' is not + * provided, 'height' (optional): the desired height, which affects scaling if 'width' is not + * provided + * @param density the density of the display, used to calculate pixel dimensions. + * @param bitmapDescriptorFactory is an instance of the BitmapDescriptorFactoryWrapper. + * @return BitmapDescriptor object from bytes data. + * @throws IllegalArgumentException if any required keys are missing in `byteData` or if the byte + * data cannot be interpreted as a valid image. + */ + @VisibleForTesting + public static BitmapDescriptor getBitmapFromBytes( + Map byteData, float density, BitmapDescriptorFactoryWrapper bitmapDescriptorFactory) { + + final String byteDataKey = "byteData"; + final String bitmapScalingKey = "bitmapScaling"; + final String imagePixelRatioKey = "imagePixelRatio"; + + if (!byteData.containsKey(byteDataKey)) { + throw new IllegalArgumentException("'bytes' requires '" + byteDataKey + "' key."); + } + if (!byteData.containsKey(bitmapScalingKey)) { + throw new IllegalArgumentException("'bytes' requires '" + bitmapScalingKey + "' key."); + } + if (!byteData.containsKey(imagePixelRatioKey)) { + throw new IllegalArgumentException("'bytes' requires '" + imagePixelRatioKey + "' key."); + } + + try { + Bitmap bitmap = toBitmap(byteData.get(byteDataKey)); + String scalingMode = toString(byteData.get(bitmapScalingKey)); + switch (scalingMode) { + case "auto": + final String widthKey = "width"; + final String heightKey = "height"; + + final Double width = + byteData.containsKey(widthKey) ? toDouble(byteData.get(widthKey)) : null; + final Double height = + byteData.containsKey(heightKey) ? toDouble(byteData.get(heightKey)) : null; + + if (width != null || height != null) { + int targetWidth = width != null ? toInt(width * density) : bitmap.getWidth(); + int targetHeight = height != null ? toInt(height * density) : bitmap.getHeight(); + + if (width != null && height == null) { + // If only width is provided, calculate height based on aspect ratio. + double aspectRatio = (double) bitmap.getHeight() / bitmap.getWidth(); + targetHeight = (int) (targetWidth * aspectRatio); + } else if (height != null && width == null) { + // If only height is provided, calculate width based on aspect ratio. + double aspectRatio = (double) bitmap.getWidth() / bitmap.getHeight(); + targetWidth = (int) (targetHeight * aspectRatio); + } + return bitmapDescriptorFactory.fromBitmap( + toScaledBitmap(bitmap, targetWidth, targetHeight)); + } else { + // Scale image using given scale ratio + final float scale = density / toFloat(byteData.get(imagePixelRatioKey)); + return bitmapDescriptorFactory.fromBitmap(toScaledBitmap(bitmap, scale)); + } + case "none": + break; + } + return bitmapDescriptorFactory.fromBitmap(bitmap); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to interpret bytes as a valid image.", e); + } + } + + /** + * Creates a BitmapDescriptor object from asset, using given details and density. + * + *

This method processes an asset specified by name and applies scaling based on the provided + * parameters. The `assetDetails` map must contain the keys 'assetName', 'bitmapScaling', and + * 'imagePixelRatio', and may optionally include 'width' and/or 'height' to explicitly set the + * dimensions of the output image. + * + * @param assetDetails a map containing the asset details and scaling instructions, with keys + * 'assetName': the name of the asset file, 'bitmapScaling': the scaling mode, either 'auto' + * or 'none', 'imagePixelRatio': used with 'auto' scaling to compute the scale ratio, 'width' + * (optional): the desired width, which affects scaling if 'height' is not provided, 'height' + * (optional): the desired height, which affects scaling if 'width' is not provided + * @param assetManager assetManager An instance of Android's AssetManager, which provides access + * to any raw asset files stored in the application's assets directory. + * @param density density the density of the display, used to calculate pixel dimensions. + * @param bitmapDescriptorFactory is an instance of the BitmapDescriptorFactoryWrapper. + * @param flutterInjector An instance of the FlutterInjectorWrapper class. + * @return BitmapDescriptor object from asset. + * @throws IllegalArgumentException if any required keys are missing in `assetDetails` or if the + * asset cannot be opened or processed as a valid image. + */ + @VisibleForTesting + public static BitmapDescriptor getBitmapFromAsset( + Map assetDetails, + AssetManager assetManager, + float density, + BitmapDescriptorFactoryWrapper bitmapDescriptorFactory, + FlutterInjectorWrapper flutterInjector) { + + final String assetNameKey = "assetName"; + final String bitmapScalingKey = "bitmapScaling"; + final String imagePixelRatioKey = "imagePixelRatio"; + + if (!assetDetails.containsKey(assetNameKey)) { + throw new IllegalArgumentException("'asset' requires '" + assetNameKey + "' key."); + } + if (!assetDetails.containsKey(bitmapScalingKey)) { + throw new IllegalArgumentException("'asset' requires '" + bitmapScalingKey + "' key."); + } + if (!assetDetails.containsKey(imagePixelRatioKey)) { + throw new IllegalArgumentException("'asset' requires '" + imagePixelRatioKey + "' key."); + } + + final String assetName = toString(assetDetails.get(assetNameKey)); + final String assetKey = flutterInjector.getLookupKeyForAsset(assetName); + + String scalingMode = toString(assetDetails.get(bitmapScalingKey)); + switch (scalingMode) { + case "auto": + final String widthKey = "width"; + final String heightKey = "height"; + + final Double width = + assetDetails.containsKey(widthKey) ? toDouble(assetDetails.get(widthKey)) : null; + final Double height = + assetDetails.containsKey(heightKey) ? toDouble(assetDetails.get(heightKey)) : null; + InputStream inputStream = null; + try { + inputStream = assetManager.open(assetKey); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + + if (width != null || height != null) { + int targetWidth = width != null ? toInt(width * density) : bitmap.getWidth(); + int targetHeight = height != null ? toInt(height * density) : bitmap.getHeight(); + + if (width != null && height == null) { + // If only width is provided, calculate height based on aspect ratio. + double aspectRatio = (double) bitmap.getHeight() / bitmap.getWidth(); + targetHeight = (int) (targetWidth * aspectRatio); + } else if (height != null && width == null) { + // If only height is provided, calculate width based on aspect ratio. + double aspectRatio = (double) bitmap.getWidth() / bitmap.getHeight(); + targetWidth = (int) (targetHeight * aspectRatio); + } + return bitmapDescriptorFactory.fromBitmap( + toScaledBitmap(bitmap, targetWidth, targetHeight)); + } else { + // Scale image using given scale. + final float scale = density / toFloat(assetDetails.get(imagePixelRatioKey)); + return bitmapDescriptorFactory.fromBitmap(toScaledBitmap(bitmap, scale)); + } + } catch (Exception e) { + throw new IllegalArgumentException("'asset' cannot open asset: " + assetName, e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + case "none": + break; + } + + return bitmapDescriptorFactory.fromAsset(assetKey); + } + private static boolean toBoolean(Object o) { return (Boolean) o; } @@ -148,6 +359,20 @@ private static int toInt(Object o) { return ((Number) o).intValue(); } + static @Nullable MapsInitializer.Renderer toMapRendererType( + @Nullable Messages.PlatformRendererType type) { + if (type == null) { + return null; + } + switch (type) { + case LATEST: + return MapsInitializer.Renderer.LATEST; + case LEGACY: + return MapsInitializer.Renderer.LEGACY; + } + return null; + } + static Object cameraPositionToJson(CameraPosition position) { if (position == null) { return null; @@ -160,13 +385,20 @@ static Object cameraPositionToJson(CameraPosition position) { return data; } - static Object latlngBoundsToJson(LatLngBounds latLngBounds) { + static Object latLngBoundsToJson(LatLngBounds latLngBounds) { final Map arguments = new HashMap<>(2); arguments.put("southwest", latLngToJson(latLngBounds.southwest)); arguments.put("northeast", latLngToJson(latLngBounds.northeast)); return arguments; } + static Messages.PlatformLatLngBounds latLngBoundsToPigeon(LatLngBounds latLngBounds) { + return new Messages.PlatformLatLngBounds.Builder() + .setNortheast(latLngToPigeon(latLngBounds.northeast)) + .setSouthwest(latLngToPigeon(latLngBounds.southwest)) + .build(); + } + static Object markerIdToJson(String markerId) { if (markerId == null) { return null; @@ -221,22 +453,79 @@ static Object latLngToJson(LatLng latLng) { return Arrays.asList(latLng.latitude, latLng.longitude); } + static Messages.PlatformLatLng latLngToPigeon(LatLng latLng) { + return new Messages.PlatformLatLng.Builder() + .setLatitude(latLng.latitude) + .setLongitude(latLng.longitude) + .build(); + } + + static LatLng latLngFromPigeon(Messages.PlatformLatLng latLng) { + return new LatLng(latLng.getLatitude(), latLng.getLongitude()); + } + + static Object clusterToJson(String clusterManagerId, Cluster cluster) { + int clusterSize = cluster.getSize(); + LatLngBounds.Builder latLngBoundsBuilder = LatLngBounds.builder(); + + String[] markerIds = new String[clusterSize]; + MarkerBuilder[] markerBuilders = cluster.getItems().toArray(new MarkerBuilder[clusterSize]); + + // Loops though cluster items and reads markers position for the LatLngBounds + // builder + // and also builds list of marker ids on the cluster. + for (int i = 0; i < clusterSize; i++) { + MarkerBuilder markerBuilder = markerBuilders[i]; + latLngBoundsBuilder.include(markerBuilder.getPosition()); + markerIds[i] = markerBuilder.markerId(); + } + + Object position = latLngToJson(cluster.getPosition()); + Object bounds = latLngBoundsToJson(latLngBoundsBuilder.build()); + + final Map data = new HashMap<>(4); + + // For dart side implementation see parseCluster method at + // packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart + data.put("clusterManagerId", clusterManagerId); + data.put("position", position); + data.put("bounds", bounds); + data.put("markerIds", Arrays.asList(markerIds)); + return data; + } + + static Messages.PlatformCluster clusterToPigeon( + String clusterManagerId, Cluster cluster) { + int clusterSize = cluster.getSize(); + String[] markerIds = new String[clusterSize]; + MarkerBuilder[] markerBuilders = cluster.getItems().toArray(new MarkerBuilder[clusterSize]); + + LatLngBounds.Builder latLngBoundsBuilder = LatLngBounds.builder(); + for (int i = 0; i < clusterSize; i++) { + MarkerBuilder markerBuilder = markerBuilders[i]; + latLngBoundsBuilder.include(markerBuilder.getPosition()); + markerIds[i] = markerBuilder.markerId(); + } + + return new Messages.PlatformCluster.Builder() + .setClusterManagerId(clusterManagerId) + .setPosition(latLngToPigeon(cluster.getPosition())) + .setBounds(latLngBoundsToPigeon(latLngBoundsBuilder.build())) + .setMarkerIds(Arrays.asList(markerIds)) + .build(); + } + static LatLng toLatLng(Object o) { final List data = toList(o); return new LatLng(toDouble(data.get(0)), toDouble(data.get(1))); } - static Point toPoint(Object o) { - Object x = toMap(o).get("x"); - Object y = toMap(o).get("y"); - return new Point((int) x, (int) y); + static Point pointFromPigeon(Messages.PlatformPoint point) { + return new Point(point.getX().intValue(), point.getY().intValue()); } - static Map pointToJson(Point point) { - final Map data = new HashMap<>(2); - data.put("x", point.x); - data.put("y", point.y); - return data; + static Messages.PlatformPoint pointToPigeon(Point point) { + return new Messages.PlatformPoint.Builder().setX((long) point.x).setY((long) point.y).build(); } private static LatLngBounds toLatLngBounds(Object o) { @@ -285,6 +574,25 @@ private static Bitmap toBitmap(Object o) { } } + private static Bitmap toScaledBitmap(Bitmap bitmap, float scale) { + // Threshold to check if scaling is necessary. + final float scalingThreshold = 0.001f; + + if (Math.abs(scale - 1) > scalingThreshold && scale > 0) { + final int newWidth = (int) (bitmap.getWidth() * scale); + final int newHeight = (int) (bitmap.getHeight() * scale); + return toScaledBitmap(bitmap, newWidth, newHeight); + } + return bitmap; + } + + private static Bitmap toScaledBitmap(Bitmap bitmap, int width, int height) { + if (width > 0 && height > 0 && (bitmap.getWidth() != width || bitmap.getHeight() != height)) { + return Bitmap.createScaledBitmap(bitmap, width, height, true); + } + return bitmap; + } + private static Point toPoint(Object o, float density) { final List data = toList(o); return new Point(toPixels(data.get(0), density), toPixels(data.get(1), density)); @@ -383,8 +691,9 @@ static void interpretGoogleMapOptions(Object o, GoogleMapOptionsSink sink) { } } - /** Returns the dartMarkerId of the interpreted marker. */ - static String interpretMarkerOptions(Object o, MarkerOptionsSink sink) { + /** Set the options in the given object to marker options sink. */ + static void interpretMarkerOptions( + Object o, MarkerOptionsSink sink, AssetManager assetManager, float density) { final Map data = toMap(o); final Object alpha = data.get("alpha"); if (alpha != null) { @@ -409,7 +718,7 @@ static String interpretMarkerOptions(Object o, MarkerOptionsSink sink) { } final Object icon = data.get("icon"); if (icon != null) { - sink.setIcon(toBitmapDescriptor(icon)); + sink.setIcon(toBitmapDescriptor(icon, assetManager, density)); } final Object infoWindow = data.get("infoWindow"); @@ -432,12 +741,6 @@ static String interpretMarkerOptions(Object o, MarkerOptionsSink sink) { if (zIndex != null) { sink.setZIndex(toFloat(zIndex)); } - final String markerId = (String) data.get("markerId"); - if (markerId == null) { - throw new IllegalArgumentException("markerId was null"); - } else { - return markerId; - } } private static void interpretInfoWindowOptions( @@ -501,7 +804,8 @@ static String interpretPolygonOptions(Object o, PolygonOptionsSink sink) { } } - static String interpretPolylineOptions(Object o, PolylineOptionsSink sink) { + static String interpretPolylineOptions( + Object o, PolylineOptionsSink sink, AssetManager assetManager, float density) { final Map data = toMap(o); final Object consumeTapEvents = data.get("consumeTapEvents"); if (consumeTapEvents != null) { @@ -513,7 +817,7 @@ static String interpretPolylineOptions(Object o, PolylineOptionsSink sink) { } final Object endCap = data.get("endCap"); if (endCap != null) { - sink.setEndCap(toCap(endCap)); + sink.setEndCap(toCap(endCap, assetManager, density)); } final Object geodesic = data.get("geodesic"); if (geodesic != null) { @@ -525,7 +829,7 @@ static String interpretPolylineOptions(Object o, PolylineOptionsSink sink) { } final Object startCap = data.get("startCap"); if (startCap != null) { - sink.setStartCap(toCap(startCap)); + sink.setStartCap(toCap(startCap, assetManager, density)); } final Object visible = data.get("visible"); if (visible != null) { @@ -648,7 +952,7 @@ private static List toPattern(Object o) { return pattern; } - private static Cap toCap(Object o) { + private static Cap toCap(Object o, AssetManager assetManager, float density) { final List data = toList(o); switch (toString(data.get(0))) { case "buttCap": @@ -659,9 +963,10 @@ private static Cap toCap(Object o) { return new SquareCap(); case "customCap": if (data.size() == 2) { - return new CustomCap(toBitmapDescriptor(data.get(1))); + return new CustomCap(toBitmapDescriptor(data.get(1), assetManager, density)); } else { - return new CustomCap(toBitmapDescriptor(data.get(1)), toFloat(data.get(2))); + return new CustomCap( + toBitmapDescriptor(data.get(1), assetManager, density), toFloat(data.get(2))); } default: throw new IllegalArgumentException("Cannot interpret " + o + " as Cap"); @@ -702,4 +1007,54 @@ static Tile interpretTile(Map data) { } return new Tile(width, height, dataArray); } + + @VisibleForTesting + static class BitmapDescriptorFactoryWrapper { + /** + * Creates a BitmapDescriptor from the provided asset key using the {@link + * BitmapDescriptorFactory}. + * + *

This method is visible for testing purposes only and should never be used outside Convert + * class. + * + * @param assetKey the key of the asset. + * @return a new instance of the {@link BitmapDescriptor}. + */ + @VisibleForTesting + public BitmapDescriptor fromAsset(String assetKey) { + return BitmapDescriptorFactory.fromAsset(assetKey); + } + + /** + * Creates a BitmapDescriptor from the provided bitmap using the {@link + * BitmapDescriptorFactory}. + * + *

This method is visible for testing purposes only and should never be used outside Convert + * class. + * + * @param bitmap the bitmap to convert. + * @return a new instance of the {@link BitmapDescriptor}. + */ + @VisibleForTesting + public BitmapDescriptor fromBitmap(Bitmap bitmap) { + return BitmapDescriptorFactory.fromBitmap(bitmap); + } + } + + @VisibleForTesting + static class FlutterInjectorWrapper { + /** + * Retrieves the lookup key for a given asset name using the {@link FlutterInjector}. + * + *

This method is visible for testing purposes only and should never be used outside Convert + * class. + * + * @param assetName the name of the asset. + * @return the lookup key for the asset. + */ + @VisibleForTesting + public String getLookupKeyForAsset(@NonNull String assetName) { + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(assetName); + } + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java index dfbaf02b9d4..02477418d42 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java @@ -23,6 +23,7 @@ class GoogleMapBuilder implements GoogleMapOptionsSink { private boolean trafficEnabled = false; private boolean buildingsEnabled = true; private Object initialMarkers; + private Object initialClusterManagers; private Object initialPolygons; private Object initialPolylines; private Object initialCircles; @@ -44,6 +45,7 @@ GoogleMapController build( controller.setTrafficEnabled(trafficEnabled); controller.setBuildingsEnabled(buildingsEnabled); controller.setTrackCameraPosition(trackCameraPosition); + controller.setInitialClusterManagers(initialClusterManagers); controller.setInitialMarkers(initialMarkers); controller.setInitialPolygons(initialPolygons); controller.setInitialPolylines(initialPolylines); @@ -162,6 +164,11 @@ public void setInitialMarkers(Object initialMarkers) { this.initialMarkers = initialMarkers; } + @Override + public void setInitialClusterManagers(Object initialClusterManagers) { + this.initialClusterManagers = initialClusterManagers; + } + @Override public void setInitialPolygons(Object initialPolygons) { this.initialPolygons = initialPolygons; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index b4102471c4b..d2b6b082a27 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -4,10 +4,13 @@ package io.flutter.plugins.googlemaps; +import static io.flutter.plugins.googlemaps.Convert.clusterToPigeon; + import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.SurfaceTexture; @@ -23,9 +26,7 @@ import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; -import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.OnMapReadyCallback; @@ -37,11 +38,17 @@ import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.TileOverlay; +import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.ClusterManager; +import com.google.maps.android.collections.MarkerManager; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugins.googlemaps.Messages.FlutterError; +import io.flutter.plugins.googlemaps.Messages.MapsApi; +import io.flutter.plugins.googlemaps.Messages.MapsInspectorApi; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Collections; @@ -49,20 +56,25 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; /** Controller of a single GoogleMaps MapView instance. */ -final class GoogleMapController - implements DefaultLifecycleObserver, - ActivityPluginBinding.OnSaveInstanceStateListener, +class GoogleMapController + implements ActivityPluginBinding.OnSaveInstanceStateListener, + ClusterManager.OnClusterItemClickListener, + ClusterManagersController.OnClusterItemRendered, + DefaultLifecycleObserver, + GoogleMapListener, GoogleMapOptionsSink, - MethodChannel.MethodCallHandler, + MapsApi, + MapsInspectorApi, OnMapReadyCallback, - GoogleMapListener, PlatformView { private static final String TAG = "GoogleMapController"; private final int id; private final MethodChannel methodChannel; + private final BinaryMessenger binaryMessenger; private final GoogleMapOptions options; @Nullable private MapView mapView; @Nullable private GoogleMap googleMap; @@ -75,22 +87,26 @@ final class GoogleMapController private boolean buildingsEnabled = true; private boolean disposed = false; @VisibleForTesting final float density; - private MethodChannel.Result mapReadyResult; + private @Nullable Messages.VoidResult mapReadyResult; private final Context context; private final LifecycleProvider lifecycleProvider; private final MarkersController markersController; + private final ClusterManagersController clusterManagersController; private final PolygonsController polygonsController; private final PolylinesController polylinesController; private final CirclesController circlesController; private final TileOverlaysController tileOverlaysController; + private MarkerManager markerManager; + private MarkerManager.Collection markerCollection; private List initialMarkers; + private List initialClusterManagers; private List initialPolygons; private List initialPolylines; private List initialCircles; private List> initialTileOverlays; // Null except between initialization and onMapReady. private @Nullable String initialMapStyle; - private @Nullable String lastStyleError; + private boolean lastSetStyleSucceeded; @VisibleForTesting List initialPadding; GoogleMapController( @@ -104,24 +120,60 @@ final class GoogleMapController this.options = options; this.mapView = new MapView(context, options); this.density = context.getResources().getDisplayMetrics().density; + this.binaryMessenger = binaryMessenger; methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_" + id); - methodChannel.setMethodCallHandler(this); + MapsApi.setUp(binaryMessenger, Integer.toString(id), this); + MapsInspectorApi.setUp(binaryMessenger, Integer.toString(id), this); + AssetManager assetManager = context.getAssets(); this.lifecycleProvider = lifecycleProvider; - this.markersController = new MarkersController(methodChannel); + this.clusterManagersController = new ClusterManagersController(methodChannel, context); + this.markersController = + new MarkersController(methodChannel, clusterManagersController, assetManager, density); this.polygonsController = new PolygonsController(methodChannel, density); - this.polylinesController = new PolylinesController(methodChannel, density); + this.polylinesController = new PolylinesController(methodChannel, assetManager, density); this.circlesController = new CirclesController(methodChannel, density); this.tileOverlaysController = new TileOverlaysController(methodChannel); } + // Constructor for testing purposes only + @VisibleForTesting + GoogleMapController( + int id, + Context context, + BinaryMessenger binaryMessenger, + MethodChannel methodChannel, + LifecycleProvider lifecycleProvider, + GoogleMapOptions options, + ClusterManagersController clusterManagersController, + MarkersController markersController, + PolygonsController polygonsController, + PolylinesController polylinesController, + CirclesController circlesController, + TileOverlaysController tileOverlaysController) { + this.id = id; + this.context = context; + this.binaryMessenger = binaryMessenger; + this.methodChannel = methodChannel; + this.options = options; + this.mapView = new MapView(context, options); + this.density = context.getResources().getDisplayMetrics().density; + this.lifecycleProvider = lifecycleProvider; + this.clusterManagersController = clusterManagersController; + this.markersController = markersController; + this.polygonsController = polygonsController; + this.polylinesController = polylinesController; + this.circlesController = circlesController; + this.tileOverlaysController = tileOverlaysController; + } + @Override public View getView() { return mapView; } @VisibleForTesting - /*package*/ void setView(MapView view) { + /* package */ void setView(MapView view) { mapView = view; } @@ -130,37 +182,35 @@ void init() { mapView.getMapAsync(this); } - private void moveCamera(CameraUpdate cameraUpdate) { - googleMap.moveCamera(cameraUpdate); - } - - private void animateCamera(CameraUpdate cameraUpdate) { - googleMap.animateCamera(cameraUpdate); - } - private CameraPosition getCameraPosition() { return trackCameraPosition ? googleMap.getCameraPosition() : null; } @Override - public void onMapReady(GoogleMap googleMap) { + public void onMapReady(@NonNull GoogleMap googleMap) { this.googleMap = googleMap; this.googleMap.setIndoorEnabled(this.indoorEnabled); this.googleMap.setTrafficEnabled(this.trafficEnabled); this.googleMap.setBuildingsEnabled(this.buildingsEnabled); installInvalidator(); - googleMap.setOnInfoWindowClickListener(this); if (mapReadyResult != null) { - mapReadyResult.success(null); + mapReadyResult.success(); mapReadyResult = null; } setGoogleMapListener(this); + markerManager = new MarkerManager(googleMap); + markerCollection = markerManager.newCollection(); updateMyLocationSettings(); - markersController.setGoogleMap(googleMap); + markersController.setCollection(markerCollection); + clusterManagersController.init(googleMap, markerManager); polygonsController.setGoogleMap(googleMap); polylinesController.setGoogleMap(googleMap); circlesController.setGoogleMap(googleMap); tileOverlaysController.setGoogleMap(googleMap); + setMarkerCollectionListener(this); + setClusterItemClickListener(this); + setClusterItemRenderedListener(this); + updateInitialClusterManagers(); updateInitialMarkers(); updateInitialPolygons(); updateInitialPolylines(); @@ -216,26 +266,28 @@ private void installInvalidator() { final MapView mapView = this.mapView; textureView.setSurfaceTextureListener( new TextureView.SurfaceTextureListener() { - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + public void onSurfaceTextureAvailable( + @NonNull SurfaceTexture surface, int width, int height) { if (internalListener != null) { internalListener.onSurfaceTextureAvailable(surface, width, height); } } - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { if (internalListener != null) { return internalListener.onSurfaceTextureDestroyed(surface); } return true; } - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + public void onSurfaceTextureSizeChanged( + @NonNull SurfaceTexture surface, int width, int height) { if (internalListener != null) { internalListener.onSurfaceTextureSizeChanged(surface, width, height); } } - public void onSurfaceTextureUpdated(SurfaceTexture surface) { + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { if (internalListener != null) { internalListener.onSurfaceTextureUpdated(surface); } @@ -245,282 +297,14 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { } @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - switch (call.method) { - case "map#waitForMap": - if (googleMap != null) { - result.success(null); - return; - } - mapReadyResult = result; - break; - case "map#update": - { - Convert.interpretGoogleMapOptions(call.argument("options"), this); - result.success(Convert.cameraPositionToJson(getCameraPosition())); - break; - } - case "map#getVisibleRegion": - { - if (googleMap != null) { - LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; - result.success(Convert.latlngBoundsToJson(latLngBounds)); - } else { - result.error( - "GoogleMap uninitialized", - "getVisibleRegion called prior to map initialization", - null); - } - break; - } - case "map#getScreenCoordinate": - { - if (googleMap != null) { - LatLng latLng = Convert.toLatLng(call.arguments); - Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); - result.success(Convert.pointToJson(screenLocation)); - } else { - result.error( - "GoogleMap uninitialized", - "getScreenCoordinate called prior to map initialization", - null); - } - break; - } - case "map#getLatLng": - { - if (googleMap != null) { - Point point = Convert.toPoint(call.arguments); - LatLng latLng = googleMap.getProjection().fromScreenLocation(point); - result.success(Convert.latLngToJson(latLng)); - } else { - result.error( - "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); - } - break; - } - case "map#takeSnapshot": - { - if (googleMap != null) { - final MethodChannel.Result _result = result; - googleMap.snapshot( - new SnapshotReadyCallback() { - @Override - public void onSnapshotReady(Bitmap bitmap) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - bitmap.recycle(); - _result.success(byteArray); - } - }); - } else { - result.error("GoogleMap uninitialized", "takeSnapshot", null); - } - break; - } - case "camera#move": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - moveCamera(cameraUpdate); - result.success(null); - break; - } - case "camera#animate": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - animateCamera(cameraUpdate); - result.success(null); - break; - } - case "markers#update": - { - List markersToAdd = call.argument("markersToAdd"); - markersController.addMarkers(markersToAdd); - List markersToChange = call.argument("markersToChange"); - markersController.changeMarkers(markersToChange); - List markerIdsToRemove = call.argument("markerIdsToRemove"); - markersController.removeMarkers(markerIdsToRemove); - result.success(null); - break; - } - case "markers#showInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.showMarkerInfoWindow((String) markerId, result); - break; - } - case "markers#hideInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.hideMarkerInfoWindow((String) markerId, result); - break; - } - case "markers#isInfoWindowShown": - { - Object markerId = call.argument("markerId"); - markersController.isInfoWindowShown((String) markerId, result); - break; - } - case "polygons#update": - { - List polygonsToAdd = call.argument("polygonsToAdd"); - polygonsController.addPolygons(polygonsToAdd); - List polygonsToChange = call.argument("polygonsToChange"); - polygonsController.changePolygons(polygonsToChange); - List polygonIdsToRemove = call.argument("polygonIdsToRemove"); - polygonsController.removePolygons(polygonIdsToRemove); - result.success(null); - break; - } - case "polylines#update": - { - List polylinesToAdd = call.argument("polylinesToAdd"); - polylinesController.addPolylines(polylinesToAdd); - List polylinesToChange = call.argument("polylinesToChange"); - polylinesController.changePolylines(polylinesToChange); - List polylineIdsToRemove = call.argument("polylineIdsToRemove"); - polylinesController.removePolylines(polylineIdsToRemove); - result.success(null); - break; - } - case "circles#update": - { - List circlesToAdd = call.argument("circlesToAdd"); - circlesController.addCircles(circlesToAdd); - List circlesToChange = call.argument("circlesToChange"); - circlesController.changeCircles(circlesToChange); - List circleIdsToRemove = call.argument("circleIdsToRemove"); - circlesController.removeCircles(circleIdsToRemove); - result.success(null); - break; - } - case "map#isCompassEnabled": - { - result.success(googleMap.getUiSettings().isCompassEnabled()); - break; - } - case "map#isMapToolbarEnabled": - { - result.success(googleMap.getUiSettings().isMapToolbarEnabled()); - break; - } - case "map#getMinMaxZoomLevels": - { - List zoomLevels = new ArrayList<>(2); - zoomLevels.add(googleMap.getMinZoomLevel()); - zoomLevels.add(googleMap.getMaxZoomLevel()); - result.success(zoomLevels); - break; - } - case "map#isZoomGesturesEnabled": - { - result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); - break; - } - case "map#isLiteModeEnabled": - { - result.success(options.getLiteMode()); - break; - } - case "map#isZoomControlsEnabled": - { - result.success(googleMap.getUiSettings().isZoomControlsEnabled()); - break; - } - case "map#isScrollGesturesEnabled": - { - result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); - break; - } - case "map#isTiltGesturesEnabled": - { - result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); - break; - } - case "map#isRotateGesturesEnabled": - { - result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); - break; - } - case "map#isMyLocationButtonEnabled": - { - result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); - break; - } - case "map#isTrafficEnabled": - { - result.success(googleMap.isTrafficEnabled()); - break; - } - case "map#isBuildingsEnabled": - { - result.success(googleMap.isBuildingsEnabled()); - break; - } - case "map#getZoomLevel": - { - result.success(googleMap.getCameraPosition().zoom); - break; - } - case "map#setStyle": - { - Object arg = call.arguments; - final String style = arg instanceof String ? (String) arg : null; - final boolean mapStyleSet = updateMapStyle(style); - ArrayList mapStyleResult = new ArrayList<>(2); - mapStyleResult.add(mapStyleSet); - if (!mapStyleSet) { - mapStyleResult.add(lastStyleError); - } - result.success(mapStyleResult); - break; - } - case "map#getStyleError": - { - result.success(lastStyleError); - break; - } - case "tileOverlays#update": - { - List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); - tileOverlaysController.addTileOverlays(tileOverlaysToAdd); - List> tileOverlaysToChange = call.argument("tileOverlaysToChange"); - tileOverlaysController.changeTileOverlays(tileOverlaysToChange); - List tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); - tileOverlaysController.removeTileOverlays(tileOverlaysToRemove); - result.success(null); - break; - } - case "tileOverlays#clearTileCache": - { - String tileOverlayId = call.argument("tileOverlayId"); - tileOverlaysController.clearTileCache(tileOverlayId); - result.success(null); - break; - } - case "map#getTileOverlayInfo": - { - String tileOverlayId = call.argument("tileOverlayId"); - result.success(tileOverlaysController.getTileOverlayInfo(tileOverlayId)); - break; - } - default: - result.notImplemented(); - } - } - - @Override - public void onMapClick(LatLng latLng) { + public void onMapClick(@NonNull LatLng latLng) { final Map arguments = new HashMap<>(2); arguments.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("map#onTap", arguments); } @Override - public void onMapLongClick(LatLng latLng) { + public void onMapLongClick(@NonNull LatLng latLng) { final Map arguments = new HashMap<>(2); arguments.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("map#onLongPress", arguments); @@ -551,12 +335,13 @@ public void onCameraMove() { @Override public void onCameraIdle() { + clusterManagersController.onCameraIdle(); methodChannel.invokeMethod("camera#onIdle", Collections.singletonMap("map", id)); } @Override public boolean onMarkerClick(Marker marker) { - return markersController.onMarkerTap(marker.getId()); + return markersController.onMapsMarkerTap(marker.getId()); } @Override @@ -595,8 +380,12 @@ public void dispose() { return; } disposed = true; - methodChannel.setMethodCallHandler(null); + MapsApi.setUp(binaryMessenger, Integer.toString(id), null); + MapsInspectorApi.setUp(binaryMessenger, Integer.toString(id), null); setGoogleMapListener(null); + setMarkerCollectionListener(null); + setClusterItemClickListener(null); + setClusterItemRenderedListener(null); destroyMapViewIfNecessary(); Lifecycle lifecycle = lifecycleProvider.getLifecycle(); if (lifecycle != null) { @@ -612,8 +401,6 @@ private void setGoogleMapListener(@Nullable GoogleMapListener listener) { googleMap.setOnCameraMoveStartedListener(listener); googleMap.setOnCameraMoveListener(listener); googleMap.setOnCameraIdleListener(listener); - googleMap.setOnMarkerClickListener(listener); - googleMap.setOnMarkerDragListener(listener); googleMap.setOnPolygonClickListener(listener); googleMap.setOnPolylineClickListener(listener); googleMap.setOnCircleClickListener(listener); @@ -621,6 +408,40 @@ private void setGoogleMapListener(@Nullable GoogleMapListener listener) { googleMap.setOnMapLongClickListener(listener); } + @VisibleForTesting + public void setMarkerCollectionListener(@Nullable GoogleMapListener listener) { + if (googleMap == null) { + Log.v(TAG, "Controller was disposed before GoogleMap was ready."); + return; + } + + markerCollection.setOnMarkerClickListener(listener); + markerCollection.setOnMarkerDragListener(listener); + markerCollection.setOnInfoWindowClickListener(listener); + } + + @VisibleForTesting + public void setClusterItemClickListener( + @Nullable ClusterManager.OnClusterItemClickListener listener) { + if (googleMap == null) { + Log.v(TAG, "Controller was disposed before GoogleMap was ready."); + return; + } + + clusterManagersController.setClusterItemClickListener(listener); + } + + @VisibleForTesting + public void setClusterItemRenderedListener( + @Nullable ClusterManagersController.OnClusterItemRendered listener) { + if (googleMap == null) { + Log.v(TAG, "Controller was disposed before GoogleMap was ready."); + return; + } + + clusterManagersController.setClusterItemRenderedListener(listener); + } + // DefaultLifecycleObserver @Override @@ -681,7 +502,7 @@ public void onRestoreInstanceState(Bundle bundle) { } @Override - public void onSaveInstanceState(Bundle bundle) { + public void onSaveInstanceState(@NonNull Bundle bundle) { if (disposed) { return; } @@ -821,7 +642,22 @@ public void setInitialMarkers(Object initialMarkers) { } private void updateInitialMarkers() { - markersController.addMarkers(initialMarkers); + markersController.addJsonMarkers(initialMarkers); + } + + @Override + public void setInitialClusterManagers(Object initialClusterManagers) { + ArrayList clusterManagers = (ArrayList) initialClusterManagers; + this.initialClusterManagers = clusterManagers != null ? new ArrayList<>(clusterManagers) : null; + if (googleMap != null) { + updateInitialClusterManagers(); + } + } + + private void updateInitialClusterManagers() { + if (initialClusterManagers != null) { + clusterManagersController.addJsonClusterManagers(initialClusterManagers); + } } @Override @@ -834,7 +670,7 @@ public void setInitialPolygons(Object initialPolygons) { } private void updateInitialPolygons() { - polygonsController.addPolygons(initialPolygons); + polygonsController.addJsonPolygons(initialPolygons); } @Override @@ -847,7 +683,7 @@ public void setInitialPolylines(Object initialPolylines) { } private void updateInitialPolylines() { - polylinesController.addPolylines(initialPolylines); + polylinesController.addJsonPolylines(initialPolylines); } @Override @@ -860,7 +696,7 @@ public void setInitialCircles(Object initialCircles) { } private void updateInitialCircles() { - circlesController.addCircles(initialCircles); + circlesController.addJsonCircles(initialCircles); } @Override @@ -872,7 +708,7 @@ public void setInitialTileOverlays(List> initialTileOverlays) { } private void updateInitialTileOverlays() { - tileOverlaysController.addTileOverlays(initialTileOverlays); + tileOverlaysController.addJsonTileOverlays(initialTileOverlays); } @SuppressLint("MissingPermission") @@ -882,7 +718,7 @@ private void updateMyLocationSettings() { // the feature won't require the permission. // Gradle is doing a static check for missing permission and in some configurations will // fail the build if the permission is missing. The following disables the Gradle lint. - //noinspection ResourceType + // noinspection ResourceType googleMap.setMyLocationEnabled(myLocationEnabled); googleMap.getUiSettings().setMyLocationButtonEnabled(myLocationButtonEnabled); } else { @@ -931,6 +767,16 @@ public void setBuildingsEnabled(boolean buildingsEnabled) { this.buildingsEnabled = buildingsEnabled; } + @Override + public void onClusterItemRendered(@NonNull MarkerBuilder markerBuilder, @NonNull Marker marker) { + markersController.onClusterItemRendered(markerBuilder, marker); + } + + @Override + public boolean onClusterItemClick(MarkerBuilder item) { + return markersController.onMarkerTap(item.markerId()); + } + public void setMapStyle(@Nullable String style) { if (googleMap == null) { initialMapStyle = style; @@ -943,9 +789,282 @@ private boolean updateMapStyle(String style) { // Dart passes an empty string to indicate that the style should be cleared. final MapStyleOptions mapStyleOptions = style == null || style.isEmpty() ? null : new MapStyleOptions(style); - final boolean set = Objects.requireNonNull(googleMap).setMapStyle(mapStyleOptions); - lastStyleError = - set ? null : "Unable to set the map style. Please check console logs for errors."; - return set; + lastSetStyleSucceeded = Objects.requireNonNull(googleMap).setMapStyle(mapStyleOptions); + return lastSetStyleSucceeded; + } + + /** MapsApi implementation */ + @Override + public void waitForMap(@NonNull Messages.VoidResult result) { + if (googleMap == null) { + mapReadyResult = result; + } else { + result.success(); + } + } + + @Override + public void updateMapConfiguration(@NonNull Messages.PlatformMapConfiguration configuration) { + Convert.interpretGoogleMapOptions(configuration.getJson(), this); + } + + @Override + public void updateCircles( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove) { + circlesController.addCircles(toAdd); + circlesController.changeCircles(toChange); + circlesController.removeCircles(idsToRemove); + } + + @Override + public void updateClusterManagers( + @NonNull List toAdd, @NonNull List idsToRemove) { + clusterManagersController.addClusterManagers(toAdd); + clusterManagersController.removeClusterManagers(idsToRemove); + } + + @Override + public void updateMarkers( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove) { + markersController.addMarkers(toAdd); + markersController.changeMarkers(toChange); + markersController.removeMarkers(idsToRemove); + } + + @Override + public void updatePolygons( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove) { + polygonsController.addPolygons(toAdd); + polygonsController.changePolygons(toChange); + polygonsController.removePolygons(idsToRemove); + } + + @Override + public void updatePolylines( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove) { + polylinesController.addPolylines(toAdd); + polylinesController.changePolylines(toChange); + polylinesController.removePolylines(idsToRemove); + } + + @Override + public void updateTileOverlays( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove) { + tileOverlaysController.addTileOverlays(toAdd); + tileOverlaysController.changeTileOverlays(toChange); + tileOverlaysController.removeTileOverlays(idsToRemove); + } + + @Override + public @NonNull Messages.PlatformPoint getScreenCoordinate( + @NonNull Messages.PlatformLatLng latLng) { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", + "getScreenCoordinate called prior to map initialization", + null); + } + Point screenLocation = + googleMap.getProjection().toScreenLocation(Convert.latLngFromPigeon(latLng)); + return Convert.pointToPigeon(screenLocation); + } + + @Override + public @NonNull Messages.PlatformLatLng getLatLng( + @NonNull Messages.PlatformPoint screenCoordinate) { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); + } + LatLng latLng = + googleMap.getProjection().fromScreenLocation(Convert.pointFromPigeon(screenCoordinate)); + return Convert.latLngToPigeon(latLng); + } + + @Override + public @NonNull Messages.PlatformLatLngBounds getVisibleRegion() { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "getVisibleRegion called prior to map initialization", null); + } + LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; + return Convert.latLngBoundsToPigeon(latLngBounds); + } + + @Override + public void moveCamera(@NonNull Messages.PlatformCameraUpdate cameraUpdate) { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "moveCamera called prior to map initialization", null); + } + googleMap.moveCamera(Convert.toCameraUpdate(cameraUpdate.getJson(), density)); + } + + @Override + public void animateCamera(@NonNull Messages.PlatformCameraUpdate cameraUpdate) { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "animateCamera called prior to map initialization", null); + } + googleMap.animateCamera(Convert.toCameraUpdate(cameraUpdate.getJson(), density)); + } + + @Override + public @NonNull Double getZoomLevel() { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "getZoomLevel called prior to map initialization", null); + } + return (double) googleMap.getCameraPosition().zoom; + } + + @Override + public void showInfoWindow(@NonNull String markerId) { + markersController.showMarkerInfoWindow(markerId); + } + + @Override + public void hideInfoWindow(@NonNull String markerId) { + markersController.hideMarkerInfoWindow(markerId); + } + + @NonNull + @Override + public Boolean isInfoWindowShown(@NonNull String markerId) { + return markersController.isInfoWindowShown(markerId); + } + + @Override + public @NonNull Boolean setStyle(@NonNull String style) { + return updateMapStyle(style); + } + + @Override + public @NonNull Boolean didLastStyleSucceed() { + return lastSetStyleSucceeded; + } + + @Override + public void clearTileCache(@NonNull String tileOverlayId) { + tileOverlaysController.clearTileCache(tileOverlayId); + } + + @Override + public void takeSnapshot(@NonNull Messages.Result result) { + if (googleMap == null) { + result.error(new FlutterError("GoogleMap uninitialized", "takeSnapshot", null)); + } else { + googleMap.snapshot( + bitmap -> { + if (bitmap == null) { + result.error(new FlutterError("Snapshot failure", "Unable to take snapshot", null)); + } else { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + byte[] byteArray = stream.toByteArray(); + bitmap.recycle(); + result.success(byteArray); + } + }); + } + } + + /** MapsInspectorApi implementation */ + @Override + public @NonNull Boolean areBuildingsEnabled() { + return Objects.requireNonNull(googleMap).isBuildingsEnabled(); + } + + @Override + public @NonNull Boolean areRotateGesturesEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isRotateGesturesEnabled(); + } + + @Override + public @NonNull Boolean areZoomControlsEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isZoomControlsEnabled(); + } + + @Override + public @NonNull Boolean areScrollGesturesEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isScrollGesturesEnabled(); + } + + @Override + public @NonNull Boolean areTiltGesturesEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isTiltGesturesEnabled(); + } + + @Override + public @NonNull Boolean areZoomGesturesEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isZoomGesturesEnabled(); + } + + @Override + public @NonNull Boolean isCompassEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isCompassEnabled(); + } + + @Override + public Boolean isLiteModeEnabled() { + return options.getLiteMode(); + } + + @Override + public @NonNull Boolean isMapToolbarEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isMapToolbarEnabled(); + } + + @Override + public @NonNull Boolean isMyLocationButtonEnabled() { + return Objects.requireNonNull(googleMap).getUiSettings().isMyLocationButtonEnabled(); + } + + @Override + public @NonNull Boolean isTrafficEnabled() { + return Objects.requireNonNull(googleMap).isTrafficEnabled(); + } + + @Override + public @Nullable Messages.PlatformTileLayer getTileOverlayInfo(@NonNull String tileOverlayId) { + TileOverlay tileOverlay = tileOverlaysController.getTileOverlay(tileOverlayId); + if (tileOverlay == null) { + return null; + } + return new Messages.PlatformTileLayer.Builder() + .setFadeIn(tileOverlay.getFadeIn()) + .setTransparency((double) tileOverlay.getTransparency()) + .setZIndex((double) tileOverlay.getZIndex()) + .setVisible(tileOverlay.isVisible()) + .build(); + } + + @Override + public @NonNull Messages.PlatformZoomRange getZoomRange() { + return new Messages.PlatformZoomRange.Builder() + .setMin((double) Objects.requireNonNull(googleMap).getMinZoomLevel()) + .setMax((double) Objects.requireNonNull(googleMap).getMaxZoomLevel()) + .build(); + } + + @Override + public @NonNull List getClusters(@NonNull String clusterManagerId) { + Set> clusters = + clusterManagersController.getClustersWithClusterManagerId(clusterManagerId); + List data = new ArrayList<>(clusters.size()); + for (Cluster cluster : clusters) { + data.add(clusterToPigeon(clusterManagerId, cluster)); + } + return data; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java index c4f6a98b6cf..b8d6485d35e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java @@ -43,6 +43,9 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition")); builder.setInitialCameraPosition(position); } + if (params.containsKey("clusterManagersToAdd")) { + builder.setInitialClusterManagers(params.get("clusterManagersToAdd")); + } if (params.containsKey("markersToAdd")) { builder.setInitialMarkers(params.get("markersToAdd")); } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java index a113c0a1c4c..e4bea6b8ffa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java @@ -5,74 +5,39 @@ package io.flutter.plugins.googlemaps; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.gms.maps.MapsInitializer; -import com.google.android.gms.maps.MapsInitializer.Renderer; import com.google.android.gms.maps.OnMapsSdkInitializedCallback; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; /** GoogleMaps initializer used to initialize the Google Maps SDK with preferred settings. */ final class GoogleMapInitializer - implements OnMapsSdkInitializedCallback, MethodChannel.MethodCallHandler { - private final MethodChannel methodChannel; + implements OnMapsSdkInitializedCallback, Messages.MapsInitializerApi { private final Context context; - private static MethodChannel.Result initializationResult; + private static Messages.Result initializationResult; private boolean rendererInitialized = false; GoogleMapInitializer(Context context, BinaryMessenger binaryMessenger) { this.context = context; - methodChannel = - new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_initializer"); - methodChannel.setMethodCallHandler(this); + Messages.MapsInitializerApi.setUp(binaryMessenger, this); } @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - switch (call.method) { - case "initializer#preferRenderer": - { - String preferredRenderer = (String) call.argument("value"); - initializeWithPreferredRenderer(preferredRenderer, result); - break; - } - default: - result.notImplemented(); - } - } - - /** - * Initializes map renderer to with preferred renderer type. Renderer can be initialized only once - * per application context. - * - *

Supported renderer types are "latest", "legacy" and "default". - */ - private void initializeWithPreferredRenderer( - String preferredRenderer, MethodChannel.Result result) { + public void initializeWithPreferredRenderer( + @Nullable Messages.PlatformRendererType type, + @NonNull Messages.Result result) { if (rendererInitialized || initializationResult != null) { result.error( - "Renderer already initialized", "Renderer initialization called multiple times", null); + new Messages.FlutterError( + "Renderer already initialized", + "Renderer initialization called multiple times", + null)); } else { initializationResult = result; - switch (preferredRenderer) { - case "latest": - initializeWithRendererRequest(Renderer.LATEST); - break; - case "legacy": - initializeWithRendererRequest(Renderer.LEGACY); - break; - case "default": - initializeWithRendererRequest(null); - break; - default: - initializationResult.error( - "Invalid renderer type", - "Renderer initialization called with invalid renderer type", - null); - initializationResult = null; - } + initializeWithRendererRequest(Convert.toMapRendererType(type)); } } @@ -83,25 +48,28 @@ private void initializeWithPreferredRenderer( * class. */ @VisibleForTesting - public void initializeWithRendererRequest(MapsInitializer.Renderer renderer) { + public void initializeWithRendererRequest(@Nullable MapsInitializer.Renderer renderer) { MapsInitializer.initialize(context, renderer, this); } /** Is called by Google Maps SDK to determine which version of the renderer was initialized. */ @Override - public void onMapsSdkInitialized(MapsInitializer.Renderer renderer) { + public void onMapsSdkInitialized(@NonNull MapsInitializer.Renderer renderer) { rendererInitialized = true; if (initializationResult != null) { switch (renderer) { case LATEST: - initializationResult.success("latest"); + initializationResult.success(Messages.PlatformRendererType.LATEST); break; case LEGACY: - initializationResult.success("legacy"); + initializationResult.success(Messages.PlatformRendererType.LEGACY); break; default: initializationResult.error( - "Unknown renderer type", "Initialized with unknown renderer type", null); + new Messages.FlutterError( + "Unknown renderer type", + "Initialized with unknown renderer type", + renderer.name())); } initializationResult = null; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java index 95c550c92fd..9f744a653b3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java @@ -49,6 +49,8 @@ interface GoogleMapOptionsSink { void setInitialMarkers(Object initialMarkers); + void setInitialClusterManagers(Object initialClusterManagers); + void setInitialPolygons(Object initialPolygons); void setInitialPolylines(Object initialPolylines); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java index 5d0a5ebdda8..36b20cfe909 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java @@ -30,41 +30,6 @@ public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware { private static final String VIEW_TYPE = "plugins.flutter.dev/google_maps_android"; - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull final io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - final Activity activity = registrar.activity(); - if (activity == null) { - // When a background flutter view tries to register the plugin, the registrar has no activity. - // We stop the registration process as this plugin is foreground only. - return; - } - if (activity instanceof LifecycleOwner) { - registrar - .platformViewRegistry() - .registerViewFactory( - VIEW_TYPE, - new GoogleMapFactory( - registrar.messenger(), - registrar.context(), - new LifecycleProvider() { - @Override - public Lifecycle getLifecycle() { - return ((LifecycleOwner) activity).getLifecycle(); - } - })); - } else { - registrar - .platformViewRegistry() - .registerViewFactory( - VIEW_TYPE, - new GoogleMapFactory( - registrar.messenger(), - registrar.context(), - new ProxyLifecycleProvider(activity))); - } - } - public GoogleMapsPlugin() {} // FlutterPlugin diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java index ecc5f01bc87..fe99cb48ada 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java @@ -7,23 +7,53 @@ import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.clustering.ClusterItem; -class MarkerBuilder implements MarkerOptionsSink { +class MarkerBuilder implements MarkerOptionsSink, ClusterItem { private final MarkerOptions markerOptions; + private String clusterManagerId; + private String markerId; private boolean consumeTapEvents; - MarkerBuilder() { + MarkerBuilder(String markerId, String clusterManagerId) { this.markerOptions = new MarkerOptions(); + this.markerId = markerId; + this.clusterManagerId = clusterManagerId; } MarkerOptions build() { return markerOptions; } + /** Update existing markerOptions with builder values */ + void update(MarkerOptions markerOptionsToUpdate) { + markerOptionsToUpdate.alpha(markerOptions.getAlpha()); + markerOptionsToUpdate.anchor(markerOptions.getAnchorU(), markerOptions.getAnchorV()); + markerOptionsToUpdate.draggable(markerOptions.isDraggable()); + markerOptionsToUpdate.flat(markerOptions.isFlat()); + markerOptionsToUpdate.icon(markerOptions.getIcon()); + markerOptionsToUpdate.infoWindowAnchor( + markerOptions.getInfoWindowAnchorU(), markerOptions.getInfoWindowAnchorV()); + markerOptionsToUpdate.title(markerOptions.getTitle()); + markerOptionsToUpdate.snippet(markerOptions.getSnippet()); + markerOptionsToUpdate.position(markerOptions.getPosition()); + markerOptionsToUpdate.rotation(markerOptions.getRotation()); + markerOptionsToUpdate.visible(markerOptions.isVisible()); + markerOptionsToUpdate.zIndex(markerOptions.getZIndex()); + } + boolean consumeTapEvents() { return consumeTapEvents; } + String clusterManagerId() { + return clusterManagerId; + } + + String markerId() { + return markerId; + } + @Override public void setAlpha(float alpha) { markerOptions.alpha(alpha); @@ -84,4 +114,24 @@ public void setVisible(boolean visible) { public void setZIndex(float zIndex) { markerOptions.zIndex(zIndex); } + + @Override + public LatLng getPosition() { + return markerOptions.getPosition(); + } + + @Override + public String getTitle() { + return markerOptions.getTitle(); + } + + @Override + public String getSnippet() { + return markerOptions.getSnippet(); + } + + @Override + public Float getZIndex() { + return markerOptions.getZIndex(); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java index 5c568a1c9a1..353ec2dfadb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java @@ -7,82 +7,139 @@ import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; +import com.google.maps.android.collections.MarkerManager; +import java.lang.ref.WeakReference; /** Controller of a single Marker on the map. */ class MarkerController implements MarkerOptionsSink { - private final Marker marker; + // Holds a weak reference to a Marker instance. The clustering library + // dynamically manages markers, adding and removing them from the map + // as needed based on user interaction or data changes. + private final WeakReference weakMarker; private final String googleMapsMarkerId; private boolean consumeTapEvents; MarkerController(Marker marker, boolean consumeTapEvents) { - this.marker = marker; + this.weakMarker = new WeakReference<>(marker); this.consumeTapEvents = consumeTapEvents; this.googleMapsMarkerId = marker.getId(); } - void remove() { - marker.remove(); + void removeFromCollection(MarkerManager.Collection markerCollection) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } + markerCollection.remove(marker); } @Override public void setAlpha(float alpha) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setAlpha(alpha); } @Override public void setAnchor(float u, float v) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setAnchor(u, v); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } this.consumeTapEvents = consumeTapEvents; } @Override public void setDraggable(boolean draggable) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setDraggable(draggable); } @Override public void setFlat(boolean flat) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setFlat(flat); } @Override public void setIcon(BitmapDescriptor bitmapDescriptor) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setIcon(bitmapDescriptor); } @Override public void setInfoWindowAnchor(float u, float v) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setInfoWindowAnchor(u, v); } @Override public void setInfoWindowText(String title, String snippet) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setTitle(title); marker.setSnippet(snippet); } @Override public void setPosition(LatLng position) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setPosition(position); } @Override public void setRotation(float rotation) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setRotation(rotation); } @Override public void setVisible(boolean visible) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setVisible(visible); } @Override public void setZIndex(float zIndex) { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.setZIndex(zIndex); } @@ -95,14 +152,26 @@ boolean consumeTapEvents() { } public void showInfoWindow() { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.showInfoWindow(); } public void hideInfoWindow() { + Marker marker = weakMarker.get(); + if (marker == null) { + return; + } marker.hideInfoWindow(); } public boolean isInfoWindowShown() { + Marker marker = weakMarker.get(); + if (marker == null) { + return false; + } return marker.isInfoWindowShown(); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java index 47ffe9b857d..5d870e88081 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java @@ -4,99 +4,128 @@ package io.flutter.plugins.googlemaps; -import com.google.android.gms.maps.GoogleMap; +import android.content.res.AssetManager; +import androidx.annotation.NonNull; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.collections.MarkerManager; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; class MarkersController { - - private final Map markerIdToController; - private final Map googleMapsMarkerIdToDartMarkerId; + private final HashMap markerIdToMarkerBuilder; + private final HashMap markerIdToController; + private final HashMap googleMapsMarkerIdToDartMarkerId; private final MethodChannel methodChannel; - private GoogleMap googleMap; + private MarkerManager.Collection markerCollection; + private final ClusterManagersController clusterManagersController; + private final AssetManager assetManager; + private final float density; - MarkersController(MethodChannel methodChannel) { + MarkersController( + MethodChannel methodChannel, + ClusterManagersController clusterManagersController, + AssetManager assetManager, + float density) { + this.markerIdToMarkerBuilder = new HashMap<>(); this.markerIdToController = new HashMap<>(); this.googleMapsMarkerIdToDartMarkerId = new HashMap<>(); this.methodChannel = methodChannel; + this.clusterManagersController = clusterManagersController; + this.assetManager = assetManager; + this.density = density; } - void setGoogleMap(GoogleMap googleMap) { - this.googleMap = googleMap; + void setCollection(MarkerManager.Collection markerCollection) { + this.markerCollection = markerCollection; } - void addMarkers(List markersToAdd) { + void addJsonMarkers(List markersToAdd) { if (markersToAdd != null) { for (Object markerToAdd : markersToAdd) { - addMarker(markerToAdd); + addJsonMarker(markerToAdd); } } } - void changeMarkers(List markersToChange) { - if (markersToChange != null) { - for (Object markerToChange : markersToChange) { - changeMarker(markerToChange); - } + void addMarkers(@NonNull List markersToAdd) { + for (Messages.PlatformMarker markerToAdd : markersToAdd) { + addJsonMarker(markerToAdd.getJson()); } } - void removeMarkers(List markerIdsToRemove) { - if (markerIdsToRemove == null) { + void changeMarkers(@NonNull List markersToChange) { + for (Messages.PlatformMarker markerToChange : markersToChange) { + changeJsonMarker(markerToChange.getJson()); + } + } + + void removeMarkers(@NonNull List markerIdsToRemove) { + for (String markerId : markerIdsToRemove) { + removeMarker(markerId); + } + } + + private void removeMarker(String markerId) { + final MarkerBuilder markerBuilder = markerIdToMarkerBuilder.remove(markerId); + if (markerBuilder == null) { return; } - for (Object rawMarkerId : markerIdsToRemove) { - if (rawMarkerId == null) { - continue; - } - String markerId = (String) rawMarkerId; - final MarkerController markerController = markerIdToController.remove(markerId); - if (markerController != null) { - markerController.remove(); - googleMapsMarkerIdToDartMarkerId.remove(markerController.getGoogleMapsMarkerId()); - } + final MarkerController markerController = markerIdToController.remove(markerId); + final String clusterManagerId = markerBuilder.clusterManagerId(); + if (clusterManagerId != null) { + // Remove marker from clusterManager. + clusterManagersController.removeItem(markerBuilder); + } else if (markerController != null && this.markerCollection != null) { + // Remove marker from map and markerCollection + markerController.removeFromCollection(markerCollection); + } + + if (markerController != null) { + googleMapsMarkerIdToDartMarkerId.remove(markerController.getGoogleMapsMarkerId()); } } - void showMarkerInfoWindow(String markerId, MethodChannel.Result result) { + void showMarkerInfoWindow(String markerId) { MarkerController markerController = markerIdToController.get(markerId); - if (markerController != null) { - markerController.showInfoWindow(); - result.success(null); - } else { - result.error("Invalid markerId", "showInfoWindow called with invalid markerId", null); + if (markerController == null) { + throw new Messages.FlutterError( + "Invalid markerId", "showInfoWindow called with invalid markerId", null); } + markerController.showInfoWindow(); } - void hideMarkerInfoWindow(String markerId, MethodChannel.Result result) { + void hideMarkerInfoWindow(String markerId) { MarkerController markerController = markerIdToController.get(markerId); - if (markerController != null) { - markerController.hideInfoWindow(); - result.success(null); - } else { - result.error("Invalid markerId", "hideInfoWindow called with invalid markerId", null); + if (markerController == null) { + throw new Messages.FlutterError( + "Invalid markerId", "hideInfoWindow called with invalid markerId", null); } + markerController.hideInfoWindow(); } - void isInfoWindowShown(String markerId, MethodChannel.Result result) { + boolean isInfoWindowShown(String markerId) { MarkerController markerController = markerIdToController.get(markerId); - if (markerController != null) { - result.success(markerController.isInfoWindowShown()); - } else { - result.error("Invalid markerId", "isInfoWindowShown called with invalid markerId", null); + if (markerController == null) { + throw new Messages.FlutterError( + "Invalid markerId", "isInfoWindowShown called with invalid markerId", null); } + return markerController.isInfoWindowShown(); } - boolean onMarkerTap(String googleMarkerId) { + boolean onMapsMarkerTap(String googleMarkerId) { String markerId = googleMapsMarkerIdToDartMarkerId.get(googleMarkerId); if (markerId == null) { return false; } + return onMarkerTap(markerId); + } + + boolean onMarkerTap(String markerId) { methodChannel.invokeMethod("marker#onTap", Convert.markerIdToJson(markerId)); MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { @@ -146,31 +175,92 @@ void onInfoWindowTap(String googleMarkerId) { methodChannel.invokeMethod("infoWindow#onTap", Convert.markerIdToJson(markerId)); } - private void addMarker(Object marker) { + /** + * Called each time clusterManager adds new visible marker to the map. Creates markerController + * for marker for realtime marker updates. + */ + public void onClusterItemRendered(MarkerBuilder markerBuilder, Marker marker) { + String markerId = markerBuilder.markerId(); + if (markerIdToMarkerBuilder.get(markerId) == markerBuilder) { + createControllerForMarker(markerBuilder.markerId(), marker, markerBuilder.consumeTapEvents()); + } + } + + private void addJsonMarker(Object marker) { if (marker == null) { return; } - MarkerBuilder markerBuilder = new MarkerBuilder(); - String markerId = Convert.interpretMarkerOptions(marker, markerBuilder); + String markerId = getMarkerId(marker); + if (markerId == null) { + throw new IllegalArgumentException("markerId was null"); + } + String clusterManagerId = getClusterManagerId(marker); + MarkerBuilder markerBuilder = new MarkerBuilder(markerId, clusterManagerId); + Convert.interpretMarkerOptions(marker, markerBuilder, assetManager, density); + addMarker(markerBuilder); + } + + private void addMarker(MarkerBuilder markerBuilder) { + if (markerBuilder == null) { + return; + } + String markerId = markerBuilder.markerId(); + + // Store marker builder for future marker rebuilds when used under clusters. + markerIdToMarkerBuilder.put(markerId, markerBuilder); + + if (markerBuilder.clusterManagerId() == null) { + addMarkerToCollection(markerId, markerBuilder); + } else { + addMarkerBuilderForCluster(markerBuilder); + } + } + + private void addMarkerToCollection(String markerId, MarkerBuilder markerBuilder) { MarkerOptions options = markerBuilder.build(); - addMarker(markerId, options, markerBuilder.consumeTapEvents()); + final Marker marker = markerCollection.addMarker(options); + createControllerForMarker(markerId, marker, markerBuilder.consumeTapEvents()); + } + + private void addMarkerBuilderForCluster(MarkerBuilder markerBuilder) { + clusterManagersController.addItem(markerBuilder); } - private void addMarker(String markerId, MarkerOptions markerOptions, boolean consumeTapEvents) { - final Marker marker = googleMap.addMarker(markerOptions); + private void createControllerForMarker(String markerId, Marker marker, boolean consumeTapEvents) { MarkerController controller = new MarkerController(marker, consumeTapEvents); markerIdToController.put(markerId, controller); googleMapsMarkerIdToDartMarkerId.put(marker.getId(), markerId); } - private void changeMarker(Object marker) { + private void changeJsonMarker(Object marker) { if (marker == null) { return; } String markerId = getMarkerId(marker); + + MarkerBuilder markerBuilder = markerIdToMarkerBuilder.get(markerId); + if (markerBuilder == null) { + return; + } + + String clusterManagerId = getClusterManagerId(marker); + String oldClusterManagerId = markerBuilder.clusterManagerId(); + + // If the cluster ID on the updated marker has changed, the marker needs to + // be removed and re-added to update its cluster manager state. + if (!(Objects.equals(clusterManagerId, oldClusterManagerId))) { + removeMarker(markerId); + addJsonMarker(marker); + return; + } + + // Update marker builder. + Convert.interpretMarkerOptions(marker, markerBuilder, assetManager, density); + + // Update existing marker on map. MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { - Convert.interpretMarkerOptions(marker, markerController); + Convert.interpretMarkerOptions(marker, markerController, assetManager, density); } } @@ -179,4 +269,10 @@ private static String getMarkerId(Object marker) { Map markerMap = (Map) marker; return (String) markerMap.get("markerId"); } + + @SuppressWarnings("unchecked") + private static String getClusterManagerId(Object marker) { + Map markerMap = (Map) marker; + return (String) markerMap.get("clusterManagerId"); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java new file mode 100644 index 00000000000..ba47fa72d2c --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -0,0 +1,2373 @@ +// 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 (v20.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package io.flutter.plugins.googlemaps; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) +public class Messages { + + /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ + public static class FlutterError extends RuntimeException { + + /** The error code. */ + public final String code; + + /** The error details. Must be a datatype supported by the api codec. */ + public final Object details; + + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { + super(message); + this.code = code; + this.details = details; + } + } + + @NonNull + protected static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList(3); + if (exception instanceof FlutterError) { + FlutterError error = (FlutterError) exception; + errorList.add(error.code); + errorList.add(error.getMessage()); + errorList.add(error.details); + } else { + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + } + return errorList; + } + + @Target(METHOD) + @Retention(CLASS) + @interface CanIgnoreReturnValue {} + + public enum PlatformRendererType { + LEGACY(0), + LATEST(1); + + final int index; + + private PlatformRendererType(final int index) { + this.index = index; + } + } + + /** + * Pigeon representation of a CameraUpdate. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformCameraUpdate { + /** + * The update data, as JSON. This should only be set from CameraUpdate.toJson, and the native + * code must intepret it according to the internal implementation details of the CameraUpdate + * class. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformCameraUpdate() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformCameraUpdate build() { + PlatformCameraUpdate pigeonReturn = new PlatformCameraUpdate(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformCameraUpdate fromList(@NonNull ArrayList __pigeon_list) { + PlatformCameraUpdate pigeonResult = new PlatformCameraUpdate(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of the Circle class. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformCircle { + /** + * The circle data, as JSON. This should only be set from Circle.toJson, and the native code + * must intepret it according to the internal implementation details of that method. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformCircle() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformCircle build() { + PlatformCircle pigeonReturn = new PlatformCircle(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformCircle fromList(@NonNull ArrayList __pigeon_list) { + PlatformCircle pigeonResult = new PlatformCircle(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of the ClusterManager class. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformClusterManager { + private @NonNull String identifier; + + public @NonNull String getIdentifier() { + return identifier; + } + + public void setIdentifier(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"identifier\" is null."); + } + this.identifier = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformClusterManager() {} + + public static final class Builder { + + private @Nullable String identifier; + + @CanIgnoreReturnValue + public @NonNull Builder setIdentifier(@NonNull String setterArg) { + this.identifier = setterArg; + return this; + } + + public @NonNull PlatformClusterManager build() { + PlatformClusterManager pigeonReturn = new PlatformClusterManager(); + pigeonReturn.setIdentifier(identifier); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(identifier); + return toListResult; + } + + static @NonNull PlatformClusterManager fromList(@NonNull ArrayList __pigeon_list) { + PlatformClusterManager pigeonResult = new PlatformClusterManager(); + Object identifier = __pigeon_list.get(0); + pigeonResult.setIdentifier((String) identifier); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of the Marker class. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformMarker { + /** + * The marker data, as JSON. This should only be set from Marker.toJson, and the native code + * must intepret it according to the internal implementation details of that method. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformMarker() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformMarker build() { + PlatformMarker pigeonReturn = new PlatformMarker(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformMarker fromList(@NonNull ArrayList __pigeon_list) { + PlatformMarker pigeonResult = new PlatformMarker(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of the Polygon class. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPolygon { + /** + * The polygon data, as JSON. This should only be set from Polygon.toJson, and the native code + * must intepret it according to the internal implementation details of that method. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPolygon() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformPolygon build() { + PlatformPolygon pigeonReturn = new PlatformPolygon(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformPolygon fromList(@NonNull ArrayList __pigeon_list) { + PlatformPolygon pigeonResult = new PlatformPolygon(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of the Polyline class. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPolyline { + /** + * The polyline data, as JSON. This should only be set from Polyline.toJson, and the native code + * must intepret it according to the internal implementation details of that method. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPolyline() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformPolyline build() { + PlatformPolyline pigeonReturn = new PlatformPolyline(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformPolyline fromList(@NonNull ArrayList __pigeon_list) { + PlatformPolyline pigeonResult = new PlatformPolyline(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of the TileOverlay class. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformTileOverlay { + /** + * The tile overlay data, as JSON. This should only be set from TileOverlay.toJson, and the + * native code must intepret it according to the internal implementation details of that method. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformTileOverlay() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformTileOverlay build() { + PlatformTileOverlay pigeonReturn = new PlatformTileOverlay(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformTileOverlay fromList(@NonNull ArrayList __pigeon_list) { + PlatformTileOverlay pigeonResult = new PlatformTileOverlay(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of LatLng. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformLatLng { + private @NonNull Double latitude; + + public @NonNull Double getLatitude() { + return latitude; + } + + public void setLatitude(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"latitude\" is null."); + } + this.latitude = setterArg; + } + + private @NonNull Double longitude; + + public @NonNull Double getLongitude() { + return longitude; + } + + public void setLongitude(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"longitude\" is null."); + } + this.longitude = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformLatLng() {} + + public static final class Builder { + + private @Nullable Double latitude; + + @CanIgnoreReturnValue + public @NonNull Builder setLatitude(@NonNull Double setterArg) { + this.latitude = setterArg; + return this; + } + + private @Nullable Double longitude; + + @CanIgnoreReturnValue + public @NonNull Builder setLongitude(@NonNull Double setterArg) { + this.longitude = setterArg; + return this; + } + + public @NonNull PlatformLatLng build() { + PlatformLatLng pigeonReturn = new PlatformLatLng(); + pigeonReturn.setLatitude(latitude); + pigeonReturn.setLongitude(longitude); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(latitude); + toListResult.add(longitude); + return toListResult; + } + + static @NonNull PlatformLatLng fromList(@NonNull ArrayList __pigeon_list) { + PlatformLatLng pigeonResult = new PlatformLatLng(); + Object latitude = __pigeon_list.get(0); + pigeonResult.setLatitude((Double) latitude); + Object longitude = __pigeon_list.get(1); + pigeonResult.setLongitude((Double) longitude); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of LatLngBounds. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformLatLngBounds { + private @NonNull PlatformLatLng northeast; + + public @NonNull PlatformLatLng getNortheast() { + return northeast; + } + + public void setNortheast(@NonNull PlatformLatLng setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"northeast\" is null."); + } + this.northeast = setterArg; + } + + private @NonNull PlatformLatLng southwest; + + public @NonNull PlatformLatLng getSouthwest() { + return southwest; + } + + public void setSouthwest(@NonNull PlatformLatLng setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"southwest\" is null."); + } + this.southwest = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformLatLngBounds() {} + + public static final class Builder { + + private @Nullable PlatformLatLng northeast; + + @CanIgnoreReturnValue + public @NonNull Builder setNortheast(@NonNull PlatformLatLng setterArg) { + this.northeast = setterArg; + return this; + } + + private @Nullable PlatformLatLng southwest; + + @CanIgnoreReturnValue + public @NonNull Builder setSouthwest(@NonNull PlatformLatLng setterArg) { + this.southwest = setterArg; + return this; + } + + public @NonNull PlatformLatLngBounds build() { + PlatformLatLngBounds pigeonReturn = new PlatformLatLngBounds(); + pigeonReturn.setNortheast(northeast); + pigeonReturn.setSouthwest(southwest); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(northeast); + toListResult.add(southwest); + return toListResult; + } + + static @NonNull PlatformLatLngBounds fromList(@NonNull ArrayList __pigeon_list) { + PlatformLatLngBounds pigeonResult = new PlatformLatLngBounds(); + Object northeast = __pigeon_list.get(0); + pigeonResult.setNortheast((PlatformLatLng) northeast); + Object southwest = __pigeon_list.get(1); + pigeonResult.setSouthwest((PlatformLatLng) southwest); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of Cluster. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformCluster { + private @NonNull String clusterManagerId; + + public @NonNull String getClusterManagerId() { + return clusterManagerId; + } + + public void setClusterManagerId(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"clusterManagerId\" is null."); + } + this.clusterManagerId = setterArg; + } + + private @NonNull PlatformLatLng position; + + public @NonNull PlatformLatLng getPosition() { + return position; + } + + public void setPosition(@NonNull PlatformLatLng setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"position\" is null."); + } + this.position = setterArg; + } + + private @NonNull PlatformLatLngBounds bounds; + + public @NonNull PlatformLatLngBounds getBounds() { + return bounds; + } + + public void setBounds(@NonNull PlatformLatLngBounds setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"bounds\" is null."); + } + this.bounds = setterArg; + } + + private @NonNull List markerIds; + + public @NonNull List getMarkerIds() { + return markerIds; + } + + public void setMarkerIds(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"markerIds\" is null."); + } + this.markerIds = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformCluster() {} + + public static final class Builder { + + private @Nullable String clusterManagerId; + + @CanIgnoreReturnValue + public @NonNull Builder setClusterManagerId(@NonNull String setterArg) { + this.clusterManagerId = setterArg; + return this; + } + + private @Nullable PlatformLatLng position; + + @CanIgnoreReturnValue + public @NonNull Builder setPosition(@NonNull PlatformLatLng setterArg) { + this.position = setterArg; + return this; + } + + private @Nullable PlatformLatLngBounds bounds; + + @CanIgnoreReturnValue + public @NonNull Builder setBounds(@NonNull PlatformLatLngBounds setterArg) { + this.bounds = setterArg; + return this; + } + + private @Nullable List markerIds; + + @CanIgnoreReturnValue + public @NonNull Builder setMarkerIds(@NonNull List setterArg) { + this.markerIds = setterArg; + return this; + } + + public @NonNull PlatformCluster build() { + PlatformCluster pigeonReturn = new PlatformCluster(); + pigeonReturn.setClusterManagerId(clusterManagerId); + pigeonReturn.setPosition(position); + pigeonReturn.setBounds(bounds); + pigeonReturn.setMarkerIds(markerIds); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(clusterManagerId); + toListResult.add(position); + toListResult.add(bounds); + toListResult.add(markerIds); + return toListResult; + } + + static @NonNull PlatformCluster fromList(@NonNull ArrayList __pigeon_list) { + PlatformCluster pigeonResult = new PlatformCluster(); + Object clusterManagerId = __pigeon_list.get(0); + pigeonResult.setClusterManagerId((String) clusterManagerId); + Object position = __pigeon_list.get(1); + pigeonResult.setPosition((PlatformLatLng) position); + Object bounds = __pigeon_list.get(2); + pigeonResult.setBounds((PlatformLatLngBounds) bounds); + Object markerIds = __pigeon_list.get(3); + pigeonResult.setMarkerIds((List) markerIds); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of MapConfiguration. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformMapConfiguration { + /** + * The configuration options, as JSON. This should only be set from _jsonForMapConfiguration, + * and the native code must intepret it according to the internal implementation details of that + * method. + */ + private @NonNull Object json; + + public @NonNull Object getJson() { + return json; + } + + public void setJson(@NonNull Object setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"json\" is null."); + } + this.json = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformMapConfiguration() {} + + public static final class Builder { + + private @Nullable Object json; + + @CanIgnoreReturnValue + public @NonNull Builder setJson(@NonNull Object setterArg) { + this.json = setterArg; + return this; + } + + public @NonNull PlatformMapConfiguration build() { + PlatformMapConfiguration pigeonReturn = new PlatformMapConfiguration(); + pigeonReturn.setJson(json); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(json); + return toListResult; + } + + static @NonNull PlatformMapConfiguration fromList(@NonNull ArrayList __pigeon_list) { + PlatformMapConfiguration pigeonResult = new PlatformMapConfiguration(); + Object json = __pigeon_list.get(0); + pigeonResult.setJson(json); + return pigeonResult; + } + } + + /** + * Pigeon representation of an x,y coordinate. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPoint { + private @NonNull Long x; + + public @NonNull Long getX() { + return x; + } + + public void setX(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"x\" is null."); + } + this.x = setterArg; + } + + private @NonNull Long y; + + public @NonNull Long getY() { + return y; + } + + public void setY(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"y\" is null."); + } + this.y = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPoint() {} + + public static final class Builder { + + private @Nullable Long x; + + @CanIgnoreReturnValue + public @NonNull Builder setX(@NonNull Long setterArg) { + this.x = setterArg; + return this; + } + + private @Nullable Long y; + + @CanIgnoreReturnValue + public @NonNull Builder setY(@NonNull Long setterArg) { + this.y = setterArg; + return this; + } + + public @NonNull PlatformPoint build() { + PlatformPoint pigeonReturn = new PlatformPoint(); + pigeonReturn.setX(x); + pigeonReturn.setY(y); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(x); + toListResult.add(y); + return toListResult; + } + + static @NonNull PlatformPoint fromList(@NonNull ArrayList __pigeon_list) { + PlatformPoint pigeonResult = new PlatformPoint(); + Object x = __pigeon_list.get(0); + pigeonResult.setX((x == null) ? null : ((x instanceof Integer) ? (Integer) x : (Long) x)); + Object y = __pigeon_list.get(1); + pigeonResult.setY((y == null) ? null : ((y instanceof Integer) ? (Integer) y : (Long) y)); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of native TileOverlay properties. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformTileLayer { + private @NonNull Boolean visible; + + public @NonNull Boolean getVisible() { + return visible; + } + + public void setVisible(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"visible\" is null."); + } + this.visible = setterArg; + } + + private @NonNull Boolean fadeIn; + + public @NonNull Boolean getFadeIn() { + return fadeIn; + } + + public void setFadeIn(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"fadeIn\" is null."); + } + this.fadeIn = setterArg; + } + + private @NonNull Double transparency; + + public @NonNull Double getTransparency() { + return transparency; + } + + public void setTransparency(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"transparency\" is null."); + } + this.transparency = setterArg; + } + + private @NonNull Double zIndex; + + public @NonNull Double getZIndex() { + return zIndex; + } + + public void setZIndex(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"zIndex\" is null."); + } + this.zIndex = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformTileLayer() {} + + public static final class Builder { + + private @Nullable Boolean visible; + + @CanIgnoreReturnValue + public @NonNull Builder setVisible(@NonNull Boolean setterArg) { + this.visible = setterArg; + return this; + } + + private @Nullable Boolean fadeIn; + + @CanIgnoreReturnValue + public @NonNull Builder setFadeIn(@NonNull Boolean setterArg) { + this.fadeIn = setterArg; + return this; + } + + private @Nullable Double transparency; + + @CanIgnoreReturnValue + public @NonNull Builder setTransparency(@NonNull Double setterArg) { + this.transparency = setterArg; + return this; + } + + private @Nullable Double zIndex; + + @CanIgnoreReturnValue + public @NonNull Builder setZIndex(@NonNull Double setterArg) { + this.zIndex = setterArg; + return this; + } + + public @NonNull PlatformTileLayer build() { + PlatformTileLayer pigeonReturn = new PlatformTileLayer(); + pigeonReturn.setVisible(visible); + pigeonReturn.setFadeIn(fadeIn); + pigeonReturn.setTransparency(transparency); + pigeonReturn.setZIndex(zIndex); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(visible); + toListResult.add(fadeIn); + toListResult.add(transparency); + toListResult.add(zIndex); + return toListResult; + } + + static @NonNull PlatformTileLayer fromList(@NonNull ArrayList __pigeon_list) { + PlatformTileLayer pigeonResult = new PlatformTileLayer(); + Object visible = __pigeon_list.get(0); + pigeonResult.setVisible((Boolean) visible); + Object fadeIn = __pigeon_list.get(1); + pigeonResult.setFadeIn((Boolean) fadeIn); + Object transparency = __pigeon_list.get(2); + pigeonResult.setTransparency((Double) transparency); + Object zIndex = __pigeon_list.get(3); + pigeonResult.setZIndex((Double) zIndex); + return pigeonResult; + } + } + + /** + * Possible outcomes of launching a URL. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformZoomRange { + private @NonNull Double min; + + public @NonNull Double getMin() { + return min; + } + + public void setMin(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"min\" is null."); + } + this.min = setterArg; + } + + private @NonNull Double max; + + public @NonNull Double getMax() { + return max; + } + + public void setMax(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"max\" is null."); + } + this.max = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformZoomRange() {} + + public static final class Builder { + + private @Nullable Double min; + + @CanIgnoreReturnValue + public @NonNull Builder setMin(@NonNull Double setterArg) { + this.min = setterArg; + return this; + } + + private @Nullable Double max; + + @CanIgnoreReturnValue + public @NonNull Builder setMax(@NonNull Double setterArg) { + this.max = setterArg; + return this; + } + + public @NonNull PlatformZoomRange build() { + PlatformZoomRange pigeonReturn = new PlatformZoomRange(); + pigeonReturn.setMin(min); + pigeonReturn.setMax(max); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(min); + toListResult.add(max); + return toListResult; + } + + static @NonNull PlatformZoomRange fromList(@NonNull ArrayList __pigeon_list) { + PlatformZoomRange pigeonResult = new PlatformZoomRange(); + Object min = __pigeon_list.get(0); + pigeonResult.setMin((Double) min); + Object max = __pigeon_list.get(1); + pigeonResult.setMax((Double) max); + return pigeonResult; + } + } + + private static class PigeonCodec extends StandardMessageCodec { + public static final PigeonCodec INSTANCE = new PigeonCodec(); + + private PigeonCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 129: + return PlatformCameraUpdate.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + return PlatformCircle.fromList((ArrayList) readValue(buffer)); + case (byte) 131: + return PlatformClusterManager.fromList((ArrayList) readValue(buffer)); + case (byte) 132: + return PlatformMarker.fromList((ArrayList) readValue(buffer)); + case (byte) 133: + return PlatformPolygon.fromList((ArrayList) readValue(buffer)); + case (byte) 134: + return PlatformPolyline.fromList((ArrayList) readValue(buffer)); + case (byte) 135: + return PlatformTileOverlay.fromList((ArrayList) readValue(buffer)); + case (byte) 136: + return PlatformLatLng.fromList((ArrayList) readValue(buffer)); + case (byte) 137: + return PlatformLatLngBounds.fromList((ArrayList) readValue(buffer)); + case (byte) 138: + return PlatformCluster.fromList((ArrayList) readValue(buffer)); + case (byte) 139: + return PlatformMapConfiguration.fromList((ArrayList) readValue(buffer)); + case (byte) 140: + return PlatformPoint.fromList((ArrayList) readValue(buffer)); + case (byte) 141: + return PlatformTileLayer.fromList((ArrayList) readValue(buffer)); + case (byte) 142: + return PlatformZoomRange.fromList((ArrayList) readValue(buffer)); + case (byte) 143: + Object value = readValue(buffer); + return value == null ? null : PlatformRendererType.values()[(int) value]; + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof PlatformCameraUpdate) { + stream.write(129); + writeValue(stream, ((PlatformCameraUpdate) value).toList()); + } else if (value instanceof PlatformCircle) { + stream.write(130); + writeValue(stream, ((PlatformCircle) value).toList()); + } else if (value instanceof PlatformClusterManager) { + stream.write(131); + writeValue(stream, ((PlatformClusterManager) value).toList()); + } else if (value instanceof PlatformMarker) { + stream.write(132); + writeValue(stream, ((PlatformMarker) value).toList()); + } else if (value instanceof PlatformPolygon) { + stream.write(133); + writeValue(stream, ((PlatformPolygon) value).toList()); + } else if (value instanceof PlatformPolyline) { + stream.write(134); + writeValue(stream, ((PlatformPolyline) value).toList()); + } else if (value instanceof PlatformTileOverlay) { + stream.write(135); + writeValue(stream, ((PlatformTileOverlay) value).toList()); + } else if (value instanceof PlatformLatLng) { + stream.write(136); + writeValue(stream, ((PlatformLatLng) value).toList()); + } else if (value instanceof PlatformLatLngBounds) { + stream.write(137); + writeValue(stream, ((PlatformLatLngBounds) value).toList()); + } else if (value instanceof PlatformCluster) { + stream.write(138); + writeValue(stream, ((PlatformCluster) value).toList()); + } else if (value instanceof PlatformMapConfiguration) { + stream.write(139); + writeValue(stream, ((PlatformMapConfiguration) value).toList()); + } else if (value instanceof PlatformPoint) { + stream.write(140); + writeValue(stream, ((PlatformPoint) value).toList()); + } else if (value instanceof PlatformTileLayer) { + stream.write(141); + writeValue(stream, ((PlatformTileLayer) value).toList()); + } else if (value instanceof PlatformZoomRange) { + stream.write(142); + writeValue(stream, ((PlatformZoomRange) value).toList()); + } else if (value instanceof PlatformRendererType) { + stream.write(143); + writeValue(stream, value == null ? null : ((PlatformRendererType) value).index); + } else { + super.writeValue(stream, value); + } + } + } + + /** Asynchronous error handling return type for non-nullable API method returns. */ + public interface Result { + /** Success case callback method for handling returns. */ + void success(@NonNull T result); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** Asynchronous error handling return type for nullable API method returns. */ + public interface NullableResult { + /** Success case callback method for handling returns. */ + void success(@Nullable T result); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** Asynchronous error handling return type for void API method returns. */ + public interface VoidResult { + /** Success case callback method for handling returns. */ + void success(); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** + * Interface for non-test interactions with the native SDK. + * + *

For test-only state queries, see [MapsInspectorApi]. + * + *

Generated interface from Pigeon that represents a handler of messages from Flutter. + */ + public interface MapsApi { + /** Returns once the map instance is available. */ + void waitForMap(@NonNull VoidResult result); + /** + * Updates the map's configuration options. + * + *

Only non-null configuration values will result in updates; options with null values will + * remain unchanged. + */ + void updateMapConfiguration(@NonNull PlatformMapConfiguration configuration); + /** Updates the set of circles on the map. */ + void updateCircles( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove); + /** Updates the set of custer managers for clusters on the map. */ + void updateClusterManagers( + @NonNull List toAdd, @NonNull List idsToRemove); + /** Updates the set of markers on the map. */ + void updateMarkers( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove); + /** Updates the set of polygonss on the map. */ + void updatePolygons( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove); + /** Updates the set of polylines on the map. */ + void updatePolylines( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove); + /** Updates the set of tile overlays on the map. */ + void updateTileOverlays( + @NonNull List toAdd, + @NonNull List toChange, + @NonNull List idsToRemove); + /** Gets the screen coordinate for the given map location. */ + @NonNull + PlatformPoint getScreenCoordinate(@NonNull PlatformLatLng latLng); + /** Gets the map location for the given screen coordinate. */ + @NonNull + PlatformLatLng getLatLng(@NonNull PlatformPoint screenCoordinate); + /** Gets the map region currently displayed on the map. */ + @NonNull + PlatformLatLngBounds getVisibleRegion(); + /** Moves the camera according to [cameraUpdate] immediately, with no animation. */ + void moveCamera(@NonNull PlatformCameraUpdate cameraUpdate); + /** Moves the camera according to [cameraUpdate], animating the update. */ + void animateCamera(@NonNull PlatformCameraUpdate cameraUpdate); + /** Gets the current map zoom level. */ + @NonNull + Double getZoomLevel(); + /** Show the info window for the marker with the given ID. */ + void showInfoWindow(@NonNull String markerId); + /** Hide the info window for the marker with the given ID. */ + void hideInfoWindow(@NonNull String markerId); + /** Returns true if the marker with the given ID is currently displaying its info window. */ + @NonNull + Boolean isInfoWindowShown(@NonNull String markerId); + /** + * Sets the style to the given map style string, where an empty string indicates that the style + * should be cleared. + * + *

Returns false if there was an error setting the style, such as an invalid style string. + */ + @NonNull + Boolean setStyle(@NonNull String style); + /** + * Returns true if the last attempt to set a style, either via initial map style or setMapStyle, + * succeeded. + * + *

This allows checking asynchronously for initial style failures, as there is no way to + * return failures from map initialization. + */ + @NonNull + Boolean didLastStyleSucceed(); + /** Clears the cache of tiles previously requseted from the tile provider. */ + void clearTileCache(@NonNull String tileOverlayId); + /** Takes a snapshot of the map and returns its image data. */ + void takeSnapshot(@NonNull Result result); + + /** The codec used by MapsApi. */ + static @NonNull MessageCodec getCodec() { + return PigeonCodec.INSTANCE; + } + /** Sets up an instance of `MapsApi` to handle messages through the `binaryMessenger`. */ + static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable MapsApi api) { + setUp(binaryMessenger, "", api); + } + + static void setUp( + @NonNull BinaryMessenger binaryMessenger, + @NonNull String messageChannelSuffix, + @Nullable MapsApi api) { + messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.waitForMap" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.waitForMap(resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateMapConfiguration" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformMapConfiguration configurationArg = (PlatformMapConfiguration) args.get(0); + try { + api.updateMapConfiguration(configurationArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateCircles" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List toAddArg = (List) args.get(0); + List toChangeArg = (List) args.get(1); + List idsToRemoveArg = (List) args.get(2); + try { + api.updateCircles(toAddArg, toChangeArg, idsToRemoveArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateClusterManagers" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List toAddArg = (List) args.get(0); + List idsToRemoveArg = (List) args.get(1); + try { + api.updateClusterManagers(toAddArg, idsToRemoveArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateMarkers" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List toAddArg = (List) args.get(0); + List toChangeArg = (List) args.get(1); + List idsToRemoveArg = (List) args.get(2); + try { + api.updateMarkers(toAddArg, toChangeArg, idsToRemoveArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updatePolygons" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List toAddArg = (List) args.get(0); + List toChangeArg = (List) args.get(1); + List idsToRemoveArg = (List) args.get(2); + try { + api.updatePolygons(toAddArg, toChangeArg, idsToRemoveArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updatePolylines" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List toAddArg = (List) args.get(0); + List toChangeArg = (List) args.get(1); + List idsToRemoveArg = (List) args.get(2); + try { + api.updatePolylines(toAddArg, toChangeArg, idsToRemoveArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateTileOverlays" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + List toAddArg = (List) args.get(0); + List toChangeArg = (List) args.get(1); + List idsToRemoveArg = (List) args.get(2); + try { + api.updateTileOverlays(toAddArg, toChangeArg, idsToRemoveArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getScreenCoordinate" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformLatLng latLngArg = (PlatformLatLng) args.get(0); + try { + PlatformPoint output = api.getScreenCoordinate(latLngArg); + 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.google_maps_flutter_android.MapsApi.getLatLng" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformPoint screenCoordinateArg = (PlatformPoint) args.get(0); + try { + PlatformLatLng output = api.getLatLng(screenCoordinateArg); + 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.google_maps_flutter_android.MapsApi.getVisibleRegion" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + PlatformLatLngBounds output = api.getVisibleRegion(); + 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.google_maps_flutter_android.MapsApi.moveCamera" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformCameraUpdate cameraUpdateArg = (PlatformCameraUpdate) args.get(0); + try { + api.moveCamera(cameraUpdateArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.animateCamera" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformCameraUpdate cameraUpdateArg = (PlatformCameraUpdate) args.get(0); + try { + api.animateCamera(cameraUpdateArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getZoomLevel" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Double output = api.getZoomLevel(); + 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.google_maps_flutter_android.MapsApi.showInfoWindow" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String markerIdArg = (String) args.get(0); + try { + api.showInfoWindow(markerIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.hideInfoWindow" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String markerIdArg = (String) args.get(0); + try { + api.hideInfoWindow(markerIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.isInfoWindowShown" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String markerIdArg = (String) args.get(0); + try { + Boolean output = api.isInfoWindowShown(markerIdArg); + 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.google_maps_flutter_android.MapsApi.setStyle" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String styleArg = (String) args.get(0); + try { + Boolean output = api.setStyle(styleArg); + 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.google_maps_flutter_android.MapsApi.didLastStyleSucceed" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.didLastStyleSucceed(); + 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.google_maps_flutter_android.MapsApi.clearTileCache" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String tileOverlayIdArg = (String) args.get(0); + try { + api.clearTileCache(tileOverlayIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.takeSnapshot" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + Result resultCallback = + new Result() { + public void success(byte[] result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.takeSnapshot(resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** + * Interface for global SDK initialization. + * + *

Generated interface from Pigeon that represents a handler of messages from Flutter. + */ + public interface MapsInitializerApi { + /** + * Initializes the Google Maps SDK with the given renderer preference. + * + *

A null renderer preference will result in the default renderer. + * + *

Calling this more than once in the lifetime of an application will result in an error. + */ + void initializeWithPreferredRenderer( + @Nullable PlatformRendererType type, @NonNull Result result); + + /** The codec used by MapsInitializerApi. */ + static @NonNull MessageCodec getCodec() { + return PigeonCodec.INSTANCE; + } + /** + * Sets up an instance of `MapsInitializerApi` to handle messages through the `binaryMessenger`. + */ + static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable MapsInitializerApi api) { + setUp(binaryMessenger, "", api); + } + + static void setUp( + @NonNull BinaryMessenger binaryMessenger, + @NonNull String messageChannelSuffix, + @Nullable MapsInitializerApi api) { + messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsInitializerApi.initializeWithPreferredRenderer" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformRendererType typeArg = (PlatformRendererType) args.get(0); + Result resultCallback = + new Result() { + public void success(PlatformRendererType result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.initializeWithPreferredRenderer(typeArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** + * Inspector API only intended for use in integration tests. + * + *

Generated interface from Pigeon that represents a handler of messages from Flutter. + */ + public interface MapsInspectorApi { + + @NonNull + Boolean areBuildingsEnabled(); + + @NonNull + Boolean areRotateGesturesEnabled(); + + @NonNull + Boolean areZoomControlsEnabled(); + + @NonNull + Boolean areScrollGesturesEnabled(); + + @NonNull + Boolean areTiltGesturesEnabled(); + + @NonNull + Boolean areZoomGesturesEnabled(); + + @NonNull + Boolean isCompassEnabled(); + + @Nullable + Boolean isLiteModeEnabled(); + + @NonNull + Boolean isMapToolbarEnabled(); + + @NonNull + Boolean isMyLocationButtonEnabled(); + + @NonNull + Boolean isTrafficEnabled(); + + @Nullable + PlatformTileLayer getTileOverlayInfo(@NonNull String tileOverlayId); + + @NonNull + PlatformZoomRange getZoomRange(); + + @NonNull + List getClusters(@NonNull String clusterManagerId); + + /** The codec used by MapsInspectorApi. */ + static @NonNull MessageCodec getCodec() { + return PigeonCodec.INSTANCE; + } + /** + * Sets up an instance of `MapsInspectorApi` to handle messages through the `binaryMessenger`. + */ + static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable MapsInspectorApi api) { + setUp(binaryMessenger, "", api); + } + + static void setUp( + @NonNull BinaryMessenger binaryMessenger, + @NonNull String messageChannelSuffix, + @Nullable MapsInspectorApi api) { + messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areBuildingsEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.areBuildingsEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.areRotateGesturesEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.areRotateGesturesEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.areZoomControlsEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.areZoomControlsEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.areScrollGesturesEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.areScrollGesturesEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.areTiltGesturesEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.areTiltGesturesEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.areZoomGesturesEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.areZoomGesturesEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.isCompassEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isCompassEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.isLiteModeEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isLiteModeEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.isMapToolbarEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isMapToolbarEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.isMyLocationButtonEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isMyLocationButtonEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.isTrafficEnabled" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isTrafficEnabled(); + 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.google_maps_flutter_android.MapsInspectorApi.getTileOverlayInfo" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String tileOverlayIdArg = (String) args.get(0); + try { + PlatformTileLayer output = api.getTileOverlayInfo(tileOverlayIdArg); + 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.google_maps_flutter_android.MapsInspectorApi.getZoomRange" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + PlatformZoomRange output = api.getZoomRange(); + 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.google_maps_flutter_android.MapsInspectorApi.getClusters" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String clusterManagerIdArg = (String) args.get(0); + try { + List output = api.getClusters(clusterManagerIdArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java index 6f855db0799..a68e3e89ea8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java @@ -4,6 +4,7 @@ package io.flutter.plugins.googlemaps; +import androidx.annotation.NonNull; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.PolygonOptions; @@ -31,31 +32,28 @@ void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } - void addPolygons(List polygonsToAdd) { + void addJsonPolygons(List polygonsToAdd) { if (polygonsToAdd != null) { for (Object polygonToAdd : polygonsToAdd) { - addPolygon(polygonToAdd); + addJsonPolygon(polygonToAdd); } } } - void changePolygons(List polygonsToChange) { - if (polygonsToChange != null) { - for (Object polygonToChange : polygonsToChange) { - changePolygon(polygonToChange); - } + void addPolygons(@NonNull List polygonsToAdd) { + for (Messages.PlatformPolygon polygonToAdd : polygonsToAdd) { + addJsonPolygon(polygonToAdd.getJson()); } } - void removePolygons(List polygonIdsToRemove) { - if (polygonIdsToRemove == null) { - return; + void changePolygons(@NonNull List polygonsToChange) { + for (Messages.PlatformPolygon polygonToChange : polygonsToChange) { + changeJsonPolygon(polygonToChange.getJson()); } - for (Object rawPolygonId : polygonIdsToRemove) { - if (rawPolygonId == null) { - continue; - } - String polygonId = (String) rawPolygonId; + } + + void removePolygons(@NonNull List polygonIdsToRemove) { + for (String polygonId : polygonIdsToRemove) { final PolygonController polygonController = polygonIdToController.remove(polygonId); if (polygonController != null) { polygonController.remove(); @@ -77,7 +75,7 @@ boolean onPolygonTap(String googlePolygonId) { return false; } - private void addPolygon(Object polygon) { + private void addJsonPolygon(Object polygon) { if (polygon == null) { return; } @@ -95,7 +93,7 @@ private void addPolygon( googleMapsPolygonIdToDartPolygonId.put(polygon.getId(), polygonId); } - private void changePolygon(Object polygon) { + private void changeJsonPolygon(Object polygon) { if (polygon == null) { return; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java index 399634933dc..043474d3dc3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java @@ -4,6 +4,8 @@ package io.flutter.plugins.googlemaps; +import android.content.res.AssetManager; +import androidx.annotation.NonNull; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; @@ -19,8 +21,10 @@ class PolylinesController { private final MethodChannel methodChannel; private GoogleMap googleMap; private final float density; + private final AssetManager assetManager; - PolylinesController(MethodChannel methodChannel, float density) { + PolylinesController(MethodChannel methodChannel, AssetManager assetManager, float density) { + this.assetManager = assetManager; this.polylineIdToController = new HashMap<>(); this.googleMapsPolylineIdToDartPolylineId = new HashMap<>(); this.methodChannel = methodChannel; @@ -31,31 +35,28 @@ void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } - void addPolylines(List polylinesToAdd) { + void addJsonPolylines(List polylinesToAdd) { if (polylinesToAdd != null) { for (Object polylineToAdd : polylinesToAdd) { - addPolyline(polylineToAdd); + addJsonPolyline(polylineToAdd); } } } - void changePolylines(List polylinesToChange) { - if (polylinesToChange != null) { - for (Object polylineToChange : polylinesToChange) { - changePolyline(polylineToChange); - } + void addPolylines(@NonNull List polylinesToAdd) { + for (Messages.PlatformPolyline polylineToAdd : polylinesToAdd) { + addJsonPolyline(polylineToAdd.getJson()); } } - void removePolylines(List polylineIdsToRemove) { - if (polylineIdsToRemove == null) { - return; + void changePolylines(@NonNull List polylinesToChange) { + for (Messages.PlatformPolyline polylineToChange : polylinesToChange) { + changeJsonPolyline(polylineToChange.getJson()); } - for (Object rawPolylineId : polylineIdsToRemove) { - if (rawPolylineId == null) { - continue; - } - String polylineId = (String) rawPolylineId; + } + + void removePolylines(@NonNull List polylineIdsToRemove) { + for (String polylineId : polylineIdsToRemove) { final PolylineController polylineController = polylineIdToController.remove(polylineId); if (polylineController != null) { polylineController.remove(); @@ -77,12 +78,13 @@ boolean onPolylineTap(String googlePolylineId) { return false; } - private void addPolyline(Object polyline) { + private void addJsonPolyline(Object polyline) { if (polyline == null) { return; } PolylineBuilder polylineBuilder = new PolylineBuilder(density); - String polylineId = Convert.interpretPolylineOptions(polyline, polylineBuilder); + String polylineId = + Convert.interpretPolylineOptions(polyline, polylineBuilder, assetManager, density); PolylineOptions options = polylineBuilder.build(); addPolyline(polylineId, options, polylineBuilder.consumeTapEvents()); } @@ -95,14 +97,14 @@ private void addPolyline( googleMapsPolylineIdToDartPolylineId.put(polyline.getId(), polylineId); } - private void changePolyline(Object polyline) { + private void changeJsonPolyline(Object polyline) { if (polyline == null) { return; } String polylineId = getPolylineId(polyline); PolylineController polylineController = polylineIdToController.get(polylineId); if (polylineController != null) { - Convert.interpretPolylineOptions(polyline, polylineController); + Convert.interpretPolylineOptions(polyline, polylineController, assetManager, density); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java index 7405b5fcc49..009dff78b3f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java @@ -6,8 +6,6 @@ import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileProvider; -import java.util.HashMap; -import java.util.Map; class TileOverlayController implements TileOverlaySink { @@ -25,14 +23,8 @@ void clearTileCache() { tileOverlay.clearTileCache(); } - Map getTileOverlayInfo() { - Map tileOverlayInfo = new HashMap<>(); - tileOverlayInfo.put("fadeIn", tileOverlay.getFadeIn()); - tileOverlayInfo.put("transparency", tileOverlay.getTransparency()); - tileOverlayInfo.put("id", tileOverlay.getId()); - tileOverlayInfo.put("zIndex", tileOverlay.getZIndex()); - tileOverlayInfo.put("visible", tileOverlay.isVisible()); - return tileOverlayInfo; + TileOverlay getTileOverlay() { + return tileOverlay; } @Override diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java index 82a3edcb32c..5dfbe2e8ee1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java @@ -4,6 +4,8 @@ package io.flutter.plugins.googlemaps; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileOverlayOptions; @@ -27,21 +29,28 @@ void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } - void addTileOverlays(List> tileOverlaysToAdd) { + void addJsonTileOverlays(List> tileOverlaysToAdd) { if (tileOverlaysToAdd == null) { return; } for (Map tileOverlayToAdd : tileOverlaysToAdd) { - addTileOverlay(tileOverlayToAdd); + addJsonTileOverlay(tileOverlayToAdd); } } - void changeTileOverlays(List> tileOverlaysToChange) { - if (tileOverlaysToChange == null) { - return; + void addTileOverlays(@NonNull List tileOverlaysToAdd) { + for (Messages.PlatformTileOverlay tileOverlayToAdd : tileOverlaysToAdd) { + @SuppressWarnings("unchecked") + final Map overlayJson = (Map) tileOverlayToAdd.getJson(); + addJsonTileOverlay(overlayJson); } - for (Map tileOverlayToChange : tileOverlaysToChange) { - changeTileOverlay(tileOverlayToChange); + } + + void changeTileOverlays(@NonNull List tileOverlaysToChange) { + for (Messages.PlatformTileOverlay tileOverlayToChange : tileOverlaysToChange) { + @SuppressWarnings("unchecked") + final Map overlayJson = (Map) tileOverlayToChange.getJson(); + changeJsonTileOverlay(overlayJson); } } @@ -67,7 +76,8 @@ void clearTileCache(String tileOverlayId) { } } - Map getTileOverlayInfo(String tileOverlayId) { + @Nullable + TileOverlay getTileOverlay(String tileOverlayId) { if (tileOverlayId == null) { return null; } @@ -75,10 +85,10 @@ Map getTileOverlayInfo(String tileOverlayId) { if (tileOverlayController == null) { return null; } - return tileOverlayController.getTileOverlayInfo(); + return tileOverlayController.getTileOverlay(); } - private void addTileOverlay(Map tileOverlayOptions) { + private void addJsonTileOverlay(Map tileOverlayOptions) { if (tileOverlayOptions == null) { return; } @@ -94,7 +104,7 @@ private void addTileOverlay(Map tileOverlayOptions) { tileOverlayIdToController.put(tileOverlayId, tileOverlayController); } - private void changeTileOverlay(Map tileOverlayOptions) { + private void changeJsonTileOverlay(Map tileOverlayOptions) { if (tileOverlayOptions == null) { return; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ClusterManagersControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ClusterManagersControllerTest.java new file mode 100644 index 00000000000..aaa50542e7f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ClusterManagersControllerTest.java @@ -0,0 +1,232 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.AssetManager; +import android.os.Build; +import androidx.test.core.app.ApplicationProvider; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.CameraPosition; +import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.algo.StaticCluster; +import com.google.maps.android.collections.MarkerManager; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodCodec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Build.VERSION_CODES.P) +public class ClusterManagersControllerTest { + private Context context; + private MethodChannel methodChannel; + private ClusterManagersController controller; + private GoogleMap googleMap; + private MarkerManager markerManager; + private MarkerManager.Collection markerCollection; + private AssetManager assetManager; + private final float density = 1; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + context = ApplicationProvider.getApplicationContext(); + assetManager = context.getAssets(); + methodChannel = + spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); + controller = spy(new ClusterManagersController(methodChannel, context)); + googleMap = mock(GoogleMap.class); + markerManager = new MarkerManager(googleMap); + markerCollection = markerManager.newCollection(); + controller.init(googleMap, markerManager); + } + + @Test + @SuppressWarnings("unchecked") + public void AddJsonClusterManagersAndMarkers() throws InterruptedException { + final String clusterManagerId = "cm_1"; + final String markerId1 = "mid_1"; + final String markerId2 = "mid_2"; + + final LatLng latLng1 = new LatLng(1.1, 2.2); + final LatLng latLng2 = new LatLng(3.3, 4.4); + + final List location1 = new ArrayList<>(); + location1.add(latLng1.latitude); + location1.add(latLng1.longitude); + + final List location2 = new ArrayList<>(); + location2.add(latLng2.latitude); + location2.add(latLng2.longitude); + + when(googleMap.getCameraPosition()) + .thenReturn(CameraPosition.builder().target(new LatLng(0, 0)).build()); + Map initialClusterManager = new HashMap<>(); + initialClusterManager.put("clusterManagerId", clusterManagerId); + List clusterManagersToAdd = new ArrayList<>(); + clusterManagersToAdd.add(initialClusterManager); + controller.addJsonClusterManagers(clusterManagersToAdd); + + MarkerBuilder markerBuilder1 = new MarkerBuilder(markerId1, clusterManagerId); + MarkerBuilder markerBuilder2 = new MarkerBuilder(markerId2, clusterManagerId); + + final Map markerData1 = + createMarkerData(markerId1, location1, clusterManagerId); + final Map markerData2 = + createMarkerData(markerId2, location2, clusterManagerId); + + Convert.interpretMarkerOptions(markerData1, markerBuilder1, assetManager, density); + Convert.interpretMarkerOptions(markerData2, markerBuilder2, assetManager, density); + + controller.addItem(markerBuilder1); + controller.addItem(markerBuilder2); + + Set> clusters = + controller.getClustersWithClusterManagerId(clusterManagerId); + assertEquals("Amount of clusters should be 1", 1, clusters.size()); + + Cluster cluster = clusters.iterator().next(); + assertNotNull("Cluster position should not be null", cluster.getPosition()); + Set markerIds = new HashSet<>(); + for (MarkerBuilder marker : cluster.getItems()) { + markerIds.add(marker.markerId()); + } + assertTrue("Marker IDs should contain markerId1", markerIds.contains(markerId1)); + assertTrue("Marker IDs should contain markerId2", markerIds.contains(markerId2)); + assertEquals("Cluster should contain exactly 2 markers", 2, cluster.getSize()); + } + + @Test + @SuppressWarnings("unchecked") + public void AddClusterManagersAndMarkers() throws InterruptedException { + final String clusterManagerId = "cm_1"; + final String markerId1 = "mid_1"; + final String markerId2 = "mid_2"; + + final LatLng latLng1 = new LatLng(1.1, 2.2); + final LatLng latLng2 = new LatLng(3.3, 4.4); + + final List location1 = new ArrayList<>(); + location1.add(latLng1.latitude); + location1.add(latLng1.longitude); + + final List location2 = new ArrayList<>(); + location2.add(latLng2.latitude); + location2.add(latLng2.longitude); + + when(googleMap.getCameraPosition()) + .thenReturn(CameraPosition.builder().target(new LatLng(0, 0)).build()); + Messages.PlatformClusterManager initialClusterManager = + new Messages.PlatformClusterManager.Builder().setIdentifier(clusterManagerId).build(); + List clusterManagersToAdd = new ArrayList<>(); + clusterManagersToAdd.add(initialClusterManager); + controller.addClusterManagers(clusterManagersToAdd); + + MarkerBuilder markerBuilder1 = new MarkerBuilder(markerId1, clusterManagerId); + MarkerBuilder markerBuilder2 = new MarkerBuilder(markerId2, clusterManagerId); + + final Map markerData1 = + createMarkerData(markerId1, location1, clusterManagerId); + final Map markerData2 = + createMarkerData(markerId2, location2, clusterManagerId); + + Convert.interpretMarkerOptions(markerData1, markerBuilder1, assetManager, density); + Convert.interpretMarkerOptions(markerData2, markerBuilder2, assetManager, density); + + controller.addItem(markerBuilder1); + controller.addItem(markerBuilder2); + + Set> clusters = + controller.getClustersWithClusterManagerId(clusterManagerId); + assertEquals("Amount of clusters should be 1", 1, clusters.size()); + + Cluster cluster = clusters.iterator().next(); + assertNotNull("Cluster position should not be null", cluster.getPosition()); + Set markerIds = new HashSet<>(); + for (MarkerBuilder marker : cluster.getItems()) { + markerIds.add(marker.markerId()); + } + assertTrue("Marker IDs should contain markerId1", markerIds.contains(markerId1)); + assertTrue("Marker IDs should contain markerId2", markerIds.contains(markerId2)); + assertEquals("Cluster should contain exactly 2 markers", 2, cluster.getSize()); + } + + @Test + @SuppressWarnings("unchecked") + public void OnClusterClickCallsMethodChannel() throws InterruptedException { + String clusterManagerId = "cm_1"; + LatLng clusterPosition = new LatLng(43.00, -87.90); + LatLng markerPosition1 = new LatLng(43.05, -87.95); + LatLng markerPosition2 = new LatLng(43.02, -87.92); + + StaticCluster cluster = new StaticCluster<>(clusterPosition); + + MarkerBuilder marker1 = new MarkerBuilder("m_1", clusterManagerId); + marker1.setPosition(markerPosition1); + cluster.add(marker1); + + MarkerBuilder marker2 = new MarkerBuilder("m_2", clusterManagerId); + marker2.setPosition(markerPosition2); + cluster.add(marker2); + + controller.onClusterClick(cluster); + Mockito.verify(methodChannel) + .invokeMethod("cluster#onTap", Convert.clusterToJson(clusterManagerId, cluster)); + } + + @Test + public void RemoveClusterManagers() { + final String clusterManagerId = "cm_1"; + + when(googleMap.getCameraPosition()) + .thenReturn(CameraPosition.builder().target(new LatLng(0, 0)).build()); + Messages.PlatformClusterManager initialClusterManager = + new Messages.PlatformClusterManager.Builder().setIdentifier(clusterManagerId).build(); + List clusterManagersToAdd = new ArrayList<>(); + clusterManagersToAdd.add(initialClusterManager); + controller.addClusterManagers(clusterManagersToAdd); + + // Verify that fetching the cluster data success and therefore ClusterManager is added. + controller.getClustersWithClusterManagerId(clusterManagerId); + + controller.removeClusterManagers(Collections.singletonList(clusterManagerId)); + // Verify that fetching the cluster data fails and therefore ClusterManager is removed. + assertThrows( + Messages.FlutterError.class, + () -> controller.getClustersWithClusterManagerId(clusterManagerId)); + } + + private Map createMarkerData( + String markerId, List location, String clusterManagerId) { + Map markerData = new HashMap<>(); + markerData.put("markerId", markerId); + markerData.put("position", location); + markerData.put("clusterManagerId", clusterManagerId); + return markerData; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java index 0d635170c1f..3680d918e2f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java @@ -4,13 +4,66 @@ package io.flutter.plugins.googlemaps; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Build; +import android.util.Base64; +import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.clustering.algo.StaticCluster; +import io.flutter.plugins.googlemaps.Convert.BitmapDescriptorFactoryWrapper; +import io.flutter.plugins.googlemaps.Convert.FlutterInjectorWrapper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +@RunWith(RobolectricTestRunner.class) +@Config(minSdk = Build.VERSION_CODES.P) public class ConvertTest { + @Mock private AssetManager assetManager; + + @Mock private BitmapDescriptorFactoryWrapper bitmapDescriptorFactoryWrapper; + + @Mock private BitmapDescriptor mockBitmapDescriptor; + + @Mock private FlutterInjectorWrapper flutterInjectorWrapper; + + AutoCloseable mockCloseable; + + // A 1x1 pixel (#8080ff) PNG image encoded in base64 + private String base64Image = generateBase64Image(); + + @Before + public void before() { + mockCloseable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + mockCloseable.close(); + } @Test public void ConvertToPointsConvertsThePointsWithFullPrecision() { @@ -26,4 +79,288 @@ public void ConvertToPointsConvertsThePointsWithFullPrecision() { Assert.assertEquals(latitude, latLng.latitude, 1e-15); Assert.assertEquals(longitude, latLng.longitude, 1e-15); } + + @Test + public void ConvertClusterToJsonReturnsCorrectData() { + String clusterManagerId = "cm_1"; + LatLng clusterPosition = new LatLng(43.00, -87.90); + LatLng markerPosition1 = new LatLng(43.05, -87.95); + LatLng markerPosition2 = new LatLng(43.02, -87.92); + + StaticCluster cluster = new StaticCluster<>(clusterPosition); + + MarkerBuilder marker1 = new MarkerBuilder("m_1", clusterManagerId); + marker1.setPosition(markerPosition1); + cluster.add(marker1); + + MarkerBuilder marker2 = new MarkerBuilder("m_2", clusterManagerId); + marker2.setPosition(markerPosition2); + cluster.add(marker2); + + Object result = Convert.clusterToJson(clusterManagerId, cluster); + Map clusterData = (Map) result; + Assert.assertEquals(clusterManagerId, clusterData.get("clusterManagerId")); + + List position = (List) clusterData.get("position"); + Assert.assertTrue(position instanceof List); + Assert.assertEquals(clusterPosition.latitude, (double) position.get(0), 1e-15); + Assert.assertEquals(clusterPosition.longitude, (double) position.get(1), 1e-15); + + Map bounds = (Map) clusterData.get("bounds"); + Assert.assertTrue(bounds instanceof Map); + List southwest = (List) bounds.get("southwest"); + List northeast = (List) bounds.get("northeast"); + Assert.assertTrue(southwest instanceof List); + Assert.assertTrue(northeast instanceof List); + + // bounding data should combine data from marker positions markerPosition1 and markerPosition2 + Assert.assertEquals(markerPosition2.latitude, (double) southwest.get(0), 1e-15); + Assert.assertEquals(markerPosition1.longitude, (double) southwest.get(1), 1e-15); + Assert.assertEquals(markerPosition1.latitude, (double) northeast.get(0), 1e-15); + Assert.assertEquals(markerPosition2.longitude, (double) northeast.get(1), 1e-15); + + Object markerIds = clusterData.get("markerIds"); + Assert.assertTrue(markerIds instanceof List); + List markerIdList = (List) markerIds; + Assert.assertEquals(2, markerIdList.size()); + Assert.assertEquals(marker1.markerId(), markerIdList.get(0)); + Assert.assertEquals(marker2.markerId(), markerIdList.get(1)); + } + + @Test + public void GetBitmapFromAssetAuto() throws Exception { + String fakeAssetName = "fake_asset_name"; + String fakeAssetKey = "fake_asset_key"; + Map assetDetails = new HashMap<>(); + assetDetails.put("assetName", fakeAssetName); + assetDetails.put("bitmapScaling", "auto"); + assetDetails.put("width", 15.0f); + assetDetails.put("height", 15.0f); + assetDetails.put("imagePixelRatio", 2.0f); + + when(flutterInjectorWrapper.getLookupKeyForAsset(fakeAssetName)).thenReturn(fakeAssetKey); + + when(assetManager.open(fakeAssetKey)).thenReturn(buildImageInputStream()); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromAsset( + assetDetails, + assetManager, + 1.0f, + bitmapDescriptorFactoryWrapper, + flutterInjectorWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromAssetAutoAndWidth() throws Exception { + String fakeAssetName = "fake_asset_name"; + String fakeAssetKey = "fake_asset_key"; + + Map assetDetails = new HashMap<>(); + assetDetails.put("assetName", fakeAssetName); + assetDetails.put("bitmapScaling", "auto"); + assetDetails.put("width", 15.0f); + assetDetails.put("imagePixelRatio", 2.0f); + + when(flutterInjectorWrapper.getLookupKeyForAsset(fakeAssetName)).thenReturn(fakeAssetKey); + + when(assetManager.open(fakeAssetKey)).thenReturn(buildImageInputStream()); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromAsset( + assetDetails, + assetManager, + 1.0f, + bitmapDescriptorFactoryWrapper, + flutterInjectorWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromAssetAutoAndHeight() throws Exception { + String fakeAssetName = "fake_asset_name"; + String fakeAssetKey = "fake_asset_key"; + + Map assetDetails = new HashMap<>(); + assetDetails.put("assetName", fakeAssetName); + assetDetails.put("bitmapScaling", "auto"); + assetDetails.put("height", 15.0f); + assetDetails.put("imagePixelRatio", 2.0f); + + when(flutterInjectorWrapper.getLookupKeyForAsset(fakeAssetName)).thenReturn(fakeAssetKey); + + when(assetManager.open(fakeAssetKey)).thenReturn(buildImageInputStream()); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromAsset( + assetDetails, + assetManager, + 1.0f, + bitmapDescriptorFactoryWrapper, + flutterInjectorWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromAssetNoScaling() throws Exception { + String fakeAssetName = "fake_asset_name"; + String fakeAssetKey = "fake_asset_key"; + + Map assetDetails = new HashMap<>(); + assetDetails.put("assetName", fakeAssetName); + assetDetails.put("bitmapScaling", "noScaling"); + assetDetails.put("imagePixelRatio", 2.0f); + + when(flutterInjectorWrapper.getLookupKeyForAsset(fakeAssetName)).thenReturn(fakeAssetKey); + + when(assetManager.open(fakeAssetKey)).thenReturn(buildImageInputStream()); + + when(bitmapDescriptorFactoryWrapper.fromAsset(any())).thenReturn(mockBitmapDescriptor); + + verify(bitmapDescriptorFactoryWrapper, never()).fromBitmap(any()); + + BitmapDescriptor result = + Convert.getBitmapFromAsset( + assetDetails, + assetManager, + 1.0f, + bitmapDescriptorFactoryWrapper, + flutterInjectorWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromBytesAuto() throws Exception { + byte[] bmpData = Base64.decode(base64Image, Base64.DEFAULT); + + Map assetDetails = new HashMap<>(); + assetDetails.put("byteData", bmpData); + assetDetails.put("bitmapScaling", "auto"); + assetDetails.put("imagePixelRatio", 2.0f); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromBytes(assetDetails, 1f, bitmapDescriptorFactoryWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromBytesAutoAndWidth() throws Exception { + byte[] bmpData = Base64.decode(base64Image, Base64.DEFAULT); + + Map assetDetails = new HashMap<>(); + assetDetails.put("byteData", bmpData); + assetDetails.put("bitmapScaling", "auto"); + assetDetails.put("imagePixelRatio", 2.0f); + assetDetails.put("width", 15.0f); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromBytes(assetDetails, 1f, bitmapDescriptorFactoryWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromBytesAutoAndHeight() throws Exception { + byte[] bmpData = Base64.decode(base64Image, Base64.DEFAULT); + + Map assetDetails = new HashMap<>(); + assetDetails.put("byteData", bmpData); + assetDetails.put("bitmapScaling", "auto"); + assetDetails.put("imagePixelRatio", 2.0f); + assetDetails.put("height", 15.0f); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromBytes(assetDetails, 1f, bitmapDescriptorFactoryWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test + public void GetBitmapFromBytesNoScaling() throws Exception { + byte[] bmpData = Base64.decode(base64Image, Base64.DEFAULT); + + Map assetDetails = new HashMap<>(); + assetDetails.put("byteData", bmpData); + assetDetails.put("bitmapScaling", "noScaling"); + assetDetails.put("imagePixelRatio", 2.0f); + + when(bitmapDescriptorFactoryWrapper.fromBitmap(any())).thenReturn(mockBitmapDescriptor); + + BitmapDescriptor result = + Convert.getBitmapFromBytes(assetDetails, 1f, bitmapDescriptorFactoryWrapper); + + Assert.assertEquals(mockBitmapDescriptor, result); + } + + @Test(expected = IllegalArgumentException.class) // Expecting an IllegalArgumentException + public void GetBitmapFromBytesThrowsErrorIfInvalidImageData() throws Exception { + String invalidBase64Image = "not valid image data"; + byte[] bmpData = Base64.decode(invalidBase64Image, Base64.DEFAULT); + + Map assetDetails = new HashMap<>(); + assetDetails.put("byteData", bmpData); + assetDetails.put("bitmapScaling", "noScaling"); + assetDetails.put("imagePixelRatio", 2.0f); + + verify(bitmapDescriptorFactoryWrapper, never()).fromBitmap(any()); + + try { + Convert.getBitmapFromBytes(assetDetails, 1f, bitmapDescriptorFactoryWrapper); + } catch (IllegalArgumentException e) { + Assert.assertEquals(e.getMessage(), "Unable to interpret bytes as a valid image."); + throw e; // rethrow the exception + } + + fail("Expected an IllegalArgumentException to be thrown"); + } + + private InputStream buildImageInputStream() { + Bitmap fakeBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + fakeBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + byte[] byteArray = byteArrayOutputStream.toByteArray(); + InputStream fakeStream = new ByteArrayInputStream(byteArray); + return fakeStream; + } + + // Helper method to generate 1x1 pixel base64 encoded png test image + private String generateBase64Image() { + int width = 1; + int height = 1; + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + // Draw on the Bitmap + Paint paint = new Paint(); + paint.setColor(Color.parseColor("#FF8080FF")); + canvas.drawRect(0, 0, width, height, paint); + + // Convert the Bitmap to PNG format + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); + byte[] pngBytes = outputStream.toByteArray(); + + // Encode the PNG bytes as a base64 string + String base64Image = Base64.encodeToString(pngBytes, Base64.DEFAULT); + + return base64Image; + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 0b940b9317d..f68ab346077 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -7,6 +7,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -15,7 +18,14 @@ import androidx.activity.ComponentActivity; import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.Marker; +import com.google.maps.android.clustering.ClusterManager; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -33,20 +43,52 @@ public class GoogleMapControllerTest { private Context context; private ComponentActivity activity; - private GoogleMapController googleMapController; AutoCloseable mockCloseable; @Mock BinaryMessenger mockMessenger; @Mock GoogleMap mockGoogleMap; + @Mock MethodChannel mockMethodChannel; + @Mock ClusterManagersController mockClusterManagersController; + @Mock MarkersController mockMarkersController; + @Mock PolygonsController mockPolygonsController; + @Mock PolylinesController mockPolylinesController; + @Mock CirclesController mockCirclesController; + @Mock TileOverlaysController mockTileOverlaysController; @Before public void before() { mockCloseable = MockitoAnnotations.openMocks(this); context = ApplicationProvider.getApplicationContext(); setUpActivityLegacy(); - googleMapController = + } + + // Returns GoogleMapController instance. + // See getGoogleMapControllerWithMockedDependencies for version with dependency injections. + public GoogleMapController getGoogleMapController() { + GoogleMapController googleMapController = new GoogleMapController(0, context, mockMessenger, activity::getLifecycle, null); googleMapController.init(); + return googleMapController; + } + + // Returns GoogleMapController instance with mocked dependency injections. + public GoogleMapController getGoogleMapControllerWithMockedDependencies() { + GoogleMapController googleMapController = + new GoogleMapController( + 0, + context, + mockMessenger, + mockMethodChannel, + activity::getLifecycle, + null, + mockClusterManagersController, + mockMarkersController, + mockPolygonsController, + mockPolylinesController, + mockCirclesController, + mockTileOverlaysController); + googleMapController.init(); + return googleMapController; } // TODO(stuartmorgan): Update this to a non-deprecated test API. @@ -63,6 +105,7 @@ public void tearDown() throws Exception { @Test public void DisposeReleaseTheMap() throws InterruptedException { + GoogleMapController googleMapController = getGoogleMapController(); googleMapController.onMapReady(mockGoogleMap); assertTrue(googleMapController != null); googleMapController.dispose(); @@ -71,6 +114,7 @@ public void DisposeReleaseTheMap() throws InterruptedException { @Test public void OnDestroyReleaseTheMap() throws InterruptedException { + GoogleMapController googleMapController = getGoogleMapController(); googleMapController.onMapReady(mockGoogleMap); assertTrue(googleMapController != null); googleMapController.onDestroy(activity); @@ -79,6 +123,7 @@ public void OnDestroyReleaseTheMap() throws InterruptedException { @Test public void OnMapReadySetsPaddingIfInitialPaddingIsThere() { + GoogleMapController googleMapController = getGoogleMapController(); float padding = 10f; int paddingWithDensity = (int) (padding * googleMapController.density); googleMapController.setInitialPadding(padding, padding, padding, padding); @@ -89,9 +134,93 @@ public void OnMapReadySetsPaddingIfInitialPaddingIsThere() { @Test public void SetPaddingStoresThePaddingValuesInInInitialPaddingWhenGoogleMapIsNull() { + GoogleMapController googleMapController = getGoogleMapController(); assertNull(googleMapController.initialPadding); googleMapController.setPadding(0f, 0f, 0f, 0f); assertNotNull(googleMapController.initialPadding); Assert.assertEquals(4, googleMapController.initialPadding.size()); } + + @Test + public void OnMapReadySetsMarkerCollectionListener() { + GoogleMapController googleMapController = getGoogleMapController(); + GoogleMapController spyGoogleMapController = spy(googleMapController); + // setMarkerCollectionListener method should be called when map is ready + spyGoogleMapController.onMapReady(mockGoogleMap); + + // Verify if the setMarkerCollectionListener method is called with listener + verify(spyGoogleMapController, times(1)) + .setMarkerCollectionListener(any(GoogleMapListener.class)); + + spyGoogleMapController.dispose(); + // Verify if the setMarkerCollectionListener is cleared on dispose + verify(spyGoogleMapController, times(1)).setMarkerCollectionListener(null); + } + + @Test + @SuppressWarnings("unchecked") + public void OnMapReadySetsClusterItemClickListener() { + GoogleMapController googleMapController = getGoogleMapController(); + GoogleMapController spyGoogleMapController = spy(googleMapController); + // setMarkerCollectionListener method should be called when map is ready + spyGoogleMapController.onMapReady(mockGoogleMap); + + // Verify if the setMarkerCollectionListener method is called with listener + verify(spyGoogleMapController, times(1)) + .setClusterItemClickListener(any(ClusterManager.OnClusterItemClickListener.class)); + + spyGoogleMapController.dispose(); + // Verify if the setMarkerCollectionListener is cleared on dispose + verify(spyGoogleMapController, times(1)).setClusterItemClickListener(null); + } + + @Test + @SuppressWarnings("unchecked") + public void OnMapReadySetsClusterItemRenderedListener() { + GoogleMapController googleMapController = getGoogleMapController(); + GoogleMapController spyGoogleMapController = spy(googleMapController); + // setMarkerCollectionListener method should be called when map is ready + spyGoogleMapController.onMapReady(mockGoogleMap); + + // Verify if the setMarkerCollectionListener method is called with listener + + verify(spyGoogleMapController, times(1)) + .setClusterItemRenderedListener(any(ClusterManagersController.OnClusterItemRendered.class)); + + spyGoogleMapController.dispose(); + // Verify if the setMarkerCollectionListener is cleared on dispose + verify(spyGoogleMapController, times(1)).setClusterItemRenderedListener(null); + } + + @Test + public void SetInitialClusterManagers() { + GoogleMapController googleMapController = getGoogleMapControllerWithMockedDependencies(); + Map initialClusterManager = new HashMap<>(); + initialClusterManager.put("clusterManagerId", "cm_1"); + List initialClusterManagers = new ArrayList<>(); + initialClusterManagers.add(initialClusterManager); + googleMapController.setInitialClusterManagers(initialClusterManagers); + googleMapController.onMapReady(mockGoogleMap); + + // Verify if the ClusterManagersController.addClusterManagers method is called with initial cluster managers. + verify(mockClusterManagersController, times(1)).addJsonClusterManagers(any()); + } + + @Test + public void OnClusterItemRenderedCallsMarkersController() { + GoogleMapController googleMapController = getGoogleMapControllerWithMockedDependencies(); + MarkerBuilder markerBuilder = new MarkerBuilder("m_1", "cm_1"); + final Marker marker = mock(Marker.class); + googleMapController.onClusterItemRendered(markerBuilder, marker); + verify(mockMarkersController, times(1)).onClusterItemRendered(markerBuilder, marker); + } + + @Test + public void OnClusterItemClickCallsMarkersController() { + GoogleMapController googleMapController = getGoogleMapControllerWithMockedDependencies(); + MarkerBuilder markerBuilder = new MarkerBuilder("m_1", "cm_1"); + + googleMapController.onClusterItemClick(markerBuilder); + verify(mockMarkersController, times(1)).onMarkerTap(markerBuilder.markerId()); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java index 2f9f5e5619f..374964cbad6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java @@ -6,7 +6,6 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -18,9 +17,6 @@ import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.MapsInitializer.Renderer; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,53 +42,33 @@ public void before() { @Test public void initializer_OnMapsSdkInitializedWithLatestRenderer() { doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LATEST); - MethodChannel.Result result = mock(MethodChannel.Result.class); - googleMapInitializer.onMethodCall( - new MethodCall( - "initializer#preferRenderer", - new HashMap() { - { - put("value", "latest"); - } - }), - result); + @SuppressWarnings("unchecked") + Messages.Result result = mock(Messages.Result.class); + googleMapInitializer.initializeWithPreferredRenderer( + Messages.PlatformRendererType.LATEST, result); googleMapInitializer.onMapsSdkInitialized(Renderer.LATEST); - verify(result, times(1)).success("latest"); - verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(Messages.PlatformRendererType.LATEST); + verify(result, never()).error(any()); } @Test public void initializer_OnMapsSdkInitializedWithLegacyRenderer() { doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LEGACY); - MethodChannel.Result result = mock(MethodChannel.Result.class); - googleMapInitializer.onMethodCall( - new MethodCall( - "initializer#preferRenderer", - new HashMap() { - { - put("value", "legacy"); - } - }), - result); + @SuppressWarnings("unchecked") + Messages.Result result = mock(Messages.Result.class); + googleMapInitializer.initializeWithPreferredRenderer( + Messages.PlatformRendererType.LEGACY, result); googleMapInitializer.onMapsSdkInitialized(Renderer.LEGACY); - verify(result, times(1)).success("legacy"); - verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(Messages.PlatformRendererType.LEGACY); + verify(result, never()).error(any()); } @Test - public void initializer_onMethodCallWithUnknownRenderer() { - doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LEGACY); - MethodChannel.Result result = mock(MethodChannel.Result.class); - googleMapInitializer.onMethodCall( - new MethodCall( - "initializer#preferRenderer", - new HashMap() { - { - put("value", "wrong_renderer"); - } - }), - result); - verify(result, never()).success(any()); - verify(result, times(1)).error(eq("Invalid renderer type"), any(), any()); + public void initializer_onMethodCallWithNoRendererPreference() { + doNothing().when(googleMapInitializer).initializeWithRendererRequest(null); + @SuppressWarnings("unchecked") + Messages.Result result = mock(Messages.Result.class); + googleMapInitializer.initializeWithPreferredRenderer(null, result); + verify(result, never()).error(any()); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java index 08b15743567..41444d9f290 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java @@ -4,36 +4,72 @@ package io.flutter.plugins.googlemaps; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import android.content.Context; +import android.content.res.AssetManager; +import android.os.Build; +import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.collections.MarkerManager; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodCodec; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +@RunWith(RobolectricTestRunner.class) +@Config(sdk = Build.VERSION_CODES.P) public class MarkersControllerTest { + private Context context; + private MethodChannel methodChannel; + private ClusterManagersController clusterManagersController; + private MarkersController controller; + private GoogleMap googleMap; + private MarkerManager markerManager; + private MarkerManager.Collection markerCollection; + private AssetManager assetManager; + private final float density = 1; - @Test - public void controller_OnMarkerDragStart() { - final MethodChannel methodChannel = + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + assetManager = ApplicationProvider.getApplicationContext().getAssets(); + context = ApplicationProvider.getApplicationContext(); + methodChannel = spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); - final MarkersController controller = new MarkersController(methodChannel); - final GoogleMap googleMap = mock(GoogleMap.class); - controller.setGoogleMap(googleMap); + clusterManagersController = spy(new ClusterManagersController(methodChannel, context)); + controller = + new MarkersController(methodChannel, clusterManagersController, assetManager, density); + googleMap = mock(GoogleMap.class); + markerManager = new MarkerManager(googleMap); + markerCollection = markerManager.newCollection(); + controller.setCollection(markerCollection); + clusterManagersController.init(googleMap, markerManager); + } + @Test + public void controller_OnMarkerDragStart() { final Marker marker = mock(Marker.class); final String googleMarkerId = "abc123"; @@ -46,7 +82,7 @@ public void controller_OnMarkerDragStart() { markerOptions.put("markerId", googleMarkerId); final List markers = Arrays.asList(markerOptions); - controller.addMarkers(markers); + controller.addJsonMarkers(markers); controller.onMarkerDragStart(googleMarkerId, latLng); final List points = new ArrayList<>(); @@ -61,12 +97,6 @@ public void controller_OnMarkerDragStart() { @Test public void controller_OnMarkerDragEnd() { - final MethodChannel methodChannel = - spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); - final MarkersController controller = new MarkersController(methodChannel); - final GoogleMap googleMap = mock(GoogleMap.class); - controller.setGoogleMap(googleMap); - final Marker marker = mock(Marker.class); final String googleMarkerId = "abc123"; @@ -79,7 +109,7 @@ public void controller_OnMarkerDragEnd() { markerOptions.put("markerId", googleMarkerId); final List markers = Arrays.asList(markerOptions); - controller.addMarkers(markers); + controller.addJsonMarkers(markers); controller.onMarkerDragEnd(googleMarkerId, latLng); final List points = new ArrayList<>(); @@ -94,12 +124,6 @@ public void controller_OnMarkerDragEnd() { @Test public void controller_OnMarkerDrag() { - final MethodChannel methodChannel = - spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); - final MarkersController controller = new MarkersController(methodChannel); - final GoogleMap googleMap = mock(GoogleMap.class); - controller.setGoogleMap(googleMap); - final Marker marker = mock(Marker.class); final String googleMarkerId = "abc123"; @@ -112,7 +136,7 @@ public void controller_OnMarkerDrag() { markerOptions.put("markerId", googleMarkerId); final List markers = Arrays.asList(markerOptions); - controller.addMarkers(markers); + controller.addJsonMarkers(markers); controller.onMarkerDrag(googleMarkerId, latLng); final List points = new ArrayList<>(); @@ -124,4 +148,116 @@ public void controller_OnMarkerDrag() { data.put("position", points); Mockito.verify(methodChannel).invokeMethod("marker#onDrag", data); } + + @Test(expected = IllegalArgumentException.class) + public void controller_AddMarkerThrowsErrorIfMarkerIdIsNull() { + final Map markerOptions = new HashMap<>(); + + final List markers = Arrays.asList(markerOptions); + try { + controller.addJsonMarkers(markers); + } catch (IllegalArgumentException e) { + assertEquals("markerId was null", e.getMessage()); + throw e; + } + } + + @Test + public void controller_AddChangeAndRemoveMarkerWithClusterManagerId() { + final Marker marker = mock(Marker.class); + + final String googleMarkerId = "abc123"; + final String clusterManagerId = "cm123"; + + when(marker.getId()).thenReturn(googleMarkerId); + + final LatLng latLng1 = new LatLng(1.1, 2.2); + final List location1 = new ArrayList<>(); + location1.add(latLng1.latitude); + location1.add(latLng1.longitude); + + final Map markerOptions1 = new HashMap<>(); + markerOptions1.put("markerId", googleMarkerId); + markerOptions1.put("position", location1); + markerOptions1.put("clusterManagerId", clusterManagerId); + + final List markers = Arrays.asList(markerOptions1); + + // Add marker and capture the markerBuilder + controller.addJsonMarkers(markers); + ArgumentCaptor captor = ArgumentCaptor.forClass(MarkerBuilder.class); + Mockito.verify(clusterManagersController, times(1)).addItem(captor.capture()); + MarkerBuilder capturedMarkerBuilder = captor.getValue(); + assertEquals(clusterManagerId, capturedMarkerBuilder.clusterManagerId()); + + // clusterManagersController calls onClusterItemRendered with created marker. + controller.onClusterItemRendered(capturedMarkerBuilder, marker); + + // Change marker to test that markerController is created and the marker can be updated + final LatLng latLng2 = new LatLng(3.3, 4.4); + final List location2 = new ArrayList<>(); + location2.add(latLng2.latitude); + location2.add(latLng2.longitude); + + final Map markerOptions2 = new HashMap<>(); + markerOptions2.put("markerId", googleMarkerId); + markerOptions2.put("position", location2); + markerOptions2.put("clusterManagerId", clusterManagerId); + final List updatedMarkers = + Collections.singletonList( + new Messages.PlatformMarker.Builder().setJson(markerOptions2).build()); + + controller.changeMarkers(updatedMarkers); + Mockito.verify(marker, times(1)).setPosition(latLng2); + + // Remove marker + controller.removeMarkers(Arrays.asList(googleMarkerId)); + + Mockito.verify(clusterManagersController, times(1)) + .removeItem( + Mockito.argThat( + markerBuilder -> markerBuilder.clusterManagerId().equals(clusterManagerId))); + } + + @Test + public void controller_AddChangeAndRemoveMarkerWithoutClusterManagerId() { + MarkerManager.Collection spyMarkerCollection = spy(markerCollection); + controller.setCollection(spyMarkerCollection); + + final Marker marker = mock(Marker.class); + + final String googleMarkerId = "abc123"; + + when(marker.getId()).thenReturn(googleMarkerId); + when(googleMap.addMarker(any(MarkerOptions.class))).thenReturn(marker); + + final Map markerOptions1 = new HashMap<>(); + markerOptions1.put("markerId", googleMarkerId); + + final List markers = Arrays.asList(markerOptions1); + controller.addJsonMarkers(markers); + + // clusterManagersController should not be called when adding the marker + Mockito.verify(clusterManagersController, times(0)).addItem(any()); + + Mockito.verify(spyMarkerCollection, times(1)).addMarker(any(MarkerOptions.class)); + + final float alpha = 0.1f; + final Map markerOptions2 = new HashMap<>(); + markerOptions2.put("markerId", googleMarkerId); + markerOptions2.put("alpha", alpha); + + final List markerUpdates = + Collections.singletonList( + new Messages.PlatformMarker.Builder().setJson(markerOptions2).build()); + controller.changeMarkers(markerUpdates); + Mockito.verify(marker, times(1)).setAlpha(alpha); + + controller.removeMarkers(Arrays.asList(googleMarkerId)); + + // clusterManagersController should not be called when removing the marker + Mockito.verify(clusterManagersController, times(0)).removeItem(any()); + + Mockito.verify(spyMarkerCollection, times(1)).remove(marker); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle index e153293498e..6ab57de0826 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle @@ -31,7 +31,7 @@ android { defaultConfig { applicationId "io.flutter.plugins.googlemapsexample" - minSdkVersion 20 + minSdkVersion flutter.minSdkVersion targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() @@ -63,6 +63,7 @@ android { androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' testImplementation 'com.google.android.gms:play-services-maps:17.0.0' + testImplementation 'com.google.maps.android:android-maps-utils:3.6.0' } lint { disable 'InvalidPackage' diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml index 815074bfad9..2055ca8d25e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml @@ -17,9 +17,6 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> - diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle index 82953dd04ae..c7727bf531b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart index e03fe8f8851..277010adfeb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; @@ -13,6 +14,8 @@ import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_example/example_google_map.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'resources/icon_image_base64.dart'; + const LatLng _kInitialMapCenter = LatLng(0, 0); const double _kInitialZoomLevel = 5; const CameraPosition _kInitialCameraPosition = @@ -967,19 +970,6 @@ void googleMapsTests() { expect(iwVisibleStatus, false); }); - testWidgets('fromAssetImage', (WidgetTester tester) async { - const double pixelRatio = 2; - const ImageConfiguration imageConfiguration = - ImageConfiguration(devicePixelRatio: pixelRatio); - final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage( - imageConfiguration, 'red_square.png'); - final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage( - imageConfiguration, 'red_square.png', - mipmaps: false); - expect((mip.toJson() as List)[2], 1); - expect((scaled.toJson() as List)[2], 2); - }); - testWidgets('testTakeSnapshot', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); @@ -1190,6 +1180,85 @@ void googleMapsTests() { }, ); + testWidgets('marker clustering', (WidgetTester tester) async { + final Key key = GlobalKey(); + const int clusterManagersAmount = 2; + const int markersPerClusterManager = 5; + final Map markers = {}; + final Set clusterManagers = {}; + + for (int i = 0; i < clusterManagersAmount; i++) { + final ClusterManagerId clusterManagerId = + ClusterManagerId('cluster_manager_$i'); + final ClusterManager clusterManager = + ClusterManager(clusterManagerId: clusterManagerId); + clusterManagers.add(clusterManager); + } + + for (final ClusterManager cm in clusterManagers) { + for (int i = 0; i < markersPerClusterManager; i++) { + final MarkerId markerId = + MarkerId('${cm.clusterManagerId.value}_marker_$i'); + final Marker marker = Marker( + markerId: markerId, + clusterManagerId: cm.clusterManagerId, + position: LatLng( + _kInitialMapCenter.latitude + i, _kInitialMapCenter.longitude)); + markers[markerId] = marker; + } + } + + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + clusterManagers: clusterManagers, + markers: Set.of(markers.values), + onMapCreated: (ExampleGoogleMapController googleMapController) { + controllerCompleter.complete(googleMapController); + }, + ), + )); + + final ExampleGoogleMapController controller = + await controllerCompleter.future; + + final GoogleMapsInspectorPlatform inspector = + GoogleMapsInspectorPlatform.instance!; + + for (final ClusterManager cm in clusterManagers) { + final List clusters = await inspector.getClusters( + mapId: controller.mapId, clusterManagerId: cm.clusterManagerId); + final int markersAmountForClusterManager = clusters + .map((Cluster cluster) => cluster.count) + .reduce((int value, int element) => value + element); + expect(markersAmountForClusterManager, markersPerClusterManager); + } + + // Remove markers from clusterManagers and test that clusterManagers are empty. + for (final MapEntry entry in markers.entries) { + markers[entry.key] = _copyMarkerWithClusterManagerId(entry.value, null); + } + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + clusterManagers: clusterManagers, + markers: Set.of(markers.values)), + )); + + for (final ClusterManager cm in clusterManagers) { + final List clusters = await inspector.getClusters( + mapId: controller.mapId, clusterManagerId: cm.clusterManagerId); + expect(clusters.length, 0); + } + }); + testWidgets( 'testCloudMapId', (WidgetTester tester) async { @@ -1267,6 +1336,112 @@ void googleMapsTests() { final String? error = await controller.getStyleError(); expect(error, isNull); }); + + testWidgets('markerWithAssetMapBitmap', (WidgetTester tester) async { + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/red_square.png', + imagePixelRatio: 1.0, + )), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + }); + + testWidgets('markerWithAssetMapBitmapCreate', (WidgetTester tester) async { + final ImageConfiguration imageConfiguration = ImageConfiguration( + devicePixelRatio: tester.view.devicePixelRatio, + ); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: await AssetMapBitmap.create( + imageConfiguration, + 'assets/red_square.png', + )), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + }); + + testWidgets('markerWithBytesMapBitmap', (WidgetTester tester) async { + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: BytesMapBitmap( + bytes, + imagePixelRatio: tester.view.devicePixelRatio, + ), + ), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + }); + + testWidgets('markerWithLegacyAsset', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final ImageConfiguration imageConfiguration = ImageConfiguration( + devicePixelRatio: tester.view.devicePixelRatio, + size: const Size(100, 100), + ); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: await BitmapDescriptor.fromAssetImage( + imageConfiguration, + 'assets/red_square.png', + )), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + + await tester.pumpAndSettle(); + }); + + testWidgets('markerWithLegacyBytes', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: BitmapDescriptor.fromBytes( + bytes, + size: const Size(100, 100), + )), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + + await tester.pumpAndSettle(); + }); } class _DebugTileProvider implements TileProvider { @@ -1312,3 +1487,26 @@ class _DebugTileProvider implements TileProvider { return Tile(width, height, byteData); } } + +Marker _copyMarkerWithClusterManagerId( + Marker marker, ClusterManagerId? clusterManagerId) { + return Marker( + markerId: marker.markerId, + alpha: marker.alpha, + anchor: marker.anchor, + consumeTapEvents: marker.consumeTapEvents, + draggable: marker.draggable, + flat: marker.flat, + icon: marker.icon, + infoWindow: marker.infoWindow, + position: marker.position, + rotation: marker.rotation, + visible: marker.visible, + zIndex: marker.zIndex, + onTap: marker.onTap, + onDragStart: marker.onDragStart, + onDrag: marker.onDrag, + onDragEnd: marker.onDragEnd, + clusterManagerId: clusterManagerId, + ); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/resources/icon_image.png b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/resources/icon_image.png new file mode 100644 index 00000000000..920b93f74d7 Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/resources/icon_image.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/resources/icon_image_base64.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/resources/icon_image_base64.dart new file mode 100644 index 00000000000..1bfc791ca38 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/resources/icon_image_base64.dart @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// This constant holds the base64-encoded data of a 16x16 PNG image of the +/// Flutter logo. +/// +/// See `icon_image.png` source in the same directory. +/// +/// To create or update this image, follow these steps: +/// 1. Create or update a 16x16 PNG image. +/// 2. Convert the image to a base64 string using a script below. +/// 3. Replace the existing base64 string below with the new one. +/// +/// Example of converting an image to base64 in Dart: +/// ```dart +/// import 'dart:convert'; +/// import 'dart:io'; +/// +/// void main() async { +/// final bytes = await File('icon_image.png').readAsBytes(); +/// final base64String = base64Encode(bytes); +/// print(base64String); +/// } +/// ``` +const String iconImageBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAIRlWElmTU' + '0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIA' + 'AIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQ' + 'AAABCgAwAEAAAAAQAAABAAAAAAx28c8QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1M' + 'OmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIH' + 'g6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8v' + 'd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcm' + 'lwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFk' + 'b2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk' + '9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6' + 'eG1wbWV0YT4KTMInWQAAAplJREFUOBF1k01ME1EQx2fe7tIPoGgTE6AJgQQSPaiH9oAtkFbsgX' + 'jygFcT0XjSkxcTDxtPJh6MR28ePMHBBA8cNLSIony0oBhEMVETP058tE132+7uG3cW24DAXN57' + '2fn9/zPz3iIcEdEl0nIxtNLr1IlVeoMadkubKmoL+u2SzAV8IjV5Ekt4GN+A8+VOUPwLarOI2G' + 'Vpqq0i4JQorwQxPtWHVZ1IKP8LNGDXGaSyqARFxDGo7MJBy4XVf3AyQ+qTHnTEXoF9cFUy3OkY' + '0oWxmWFtD5xNoc1sQ6AOn1+hCNTkkhKow8KFZV77tVs2O9dhFvBm0IA/U0RhZ7/ocEx23oUDlh' + 'h8HkNjZIN8Lb3gOU8gOp7AKJHCB2/aNZkTftHumNzzbtl2CBPZHqxw8mHhVZBeoz6w5DvhE2FZ' + 'lQYPjKdd2/qRyKZ6KsPv7TEk7EYEk0A0EUmJduHRy1i4oLKqgmC59ZggAdwrC9pFuWy1iUT2rA' + 'uv0h2UdNtNqxCBBkgqorjOMOgksN7CxQ90vEb00U3c3LIwyo9o8FXxQVNr8Coqyk+S5EPBXnjt' + 'xRmc4TegI7qWbvBkeeUbGMnTCd4nZnYeDOWIEtlC6cKK/JJepY3hZSvN33jovO6L0XFqPKqBTO' + 'FuapUoPr1lxDM7cmC2TAOz25cYSGa++feBew/cjpc0V+mNT29/HZp3KDFTNLvuTRPEHy5065lj' + 'Xn4y41XM+wP/AlcycRmdc3MUhvLm/J/ceu/3qUVT62oP2EZpjSylHybHSpDUVcjq9gEBVo0+Xt' + 'JyN2IWRO+3QUforRoKnZLVsglaMECW+YmMSj9M3SrC6Lg71CMiqWfUrJ6ywzefhnZ+G69BaKdB' + 'WhXQAn6wzDUpfUPw7MrmX/WhbfmKblw+AAAAAElFTkSuQmCC'; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/clustering.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/clustering.dart new file mode 100644 index 00000000000..1cd95118eb1 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/clustering.dart @@ -0,0 +1,279 @@ +// 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:math'; + +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +import 'example_google_map.dart'; +import 'page.dart'; + +/// Page for demonstrating marker clustering support. +class ClusteringPage extends GoogleMapExampleAppPage { + /// Default Constructor. + const ClusteringPage({Key? key}) + : super(const Icon(Icons.place), 'Manage clustering', key: key); + + @override + Widget build(BuildContext context) { + return const ClusteringBody(); + } +} + +/// Body of the clustering page. +class ClusteringBody extends StatefulWidget { + /// Default Constructor. + const ClusteringBody({super.key}); + + @override + State createState() => ClusteringBodyState(); +} + +/// State of the clustering page. +class ClusteringBodyState extends State { + /// Default Constructor. + ClusteringBodyState(); + + /// Starting point from where markers are added. + static const LatLng center = LatLng(-33.86, 151.1547171); + + /// Initial camera position. + static const CameraPosition initialCameraPosition = CameraPosition( + target: LatLng(-33.852, 151.25), + zoom: 11.0, + ); + + /// Marker offset factor for randomizing marker placing. + static const double _markerOffsetFactor = 0.05; + + /// Offset for longitude when placing markers to different cluster managers. + static const double _clusterManagerLongitudeOffset = 0.1; + + /// Maximum amount of cluster managers. + static const int _clusterManagerMaxCount = 3; + + /// Amount of markers to be added to the cluster manager at once. + static const int _markersToAddToClusterManagerCount = 10; + + /// Fully visible alpha value. + static const double _fullyVisibleAlpha = 1.0; + + /// Half visible alpha value. + static const double _halfVisibleAlpha = 0.5; + + /// Google map controller. + ExampleGoogleMapController? controller; + + /// Map of clusterManagers with identifier as the key. + Map clusterManagers = + {}; + + /// Map of markers with identifier as the key. + Map markers = {}; + + /// Id of the currently selected marker. + MarkerId? selectedMarker; + + /// Counter for added cluster manager ids. + int _clusterManagerIdCounter = 1; + + /// Counter for added markers ids. + int _markerIdCounter = 1; + + /// Cluster that was tapped most recently. + Cluster? lastCluster; + + // ignore: use_setters_to_change_properties + void _onMapCreated(ExampleGoogleMapController controller) { + this.controller = controller; + } + + @override + void dispose() { + super.dispose(); + } + + void _onMarkerTapped(MarkerId markerId) { + final Marker? tappedMarker = markers[markerId]; + if (tappedMarker != null) { + setState(() { + final MarkerId? previousMarkerId = selectedMarker; + if (previousMarkerId != null && markers.containsKey(previousMarkerId)) { + final Marker resetOld = markers[previousMarkerId]! + .copyWith(iconParam: BitmapDescriptor.defaultMarker); + markers[previousMarkerId] = resetOld; + } + selectedMarker = markerId; + final Marker newMarker = tappedMarker.copyWith( + iconParam: BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueGreen, + ), + ); + markers[markerId] = newMarker; + }); + } + } + + void _addClusterManager() { + if (clusterManagers.length == _clusterManagerMaxCount) { + return; + } + + final String clusterManagerIdVal = + 'cluster_manager_id_$_clusterManagerIdCounter'; + _clusterManagerIdCounter++; + final ClusterManagerId clusterManagerId = + ClusterManagerId(clusterManagerIdVal); + + final ClusterManager clusterManager = ClusterManager( + clusterManagerId: clusterManagerId, + onClusterTap: (Cluster cluster) => setState(() { + lastCluster = cluster; + }), + ); + + setState(() { + clusterManagers[clusterManagerId] = clusterManager; + }); + _addMarkersToCluster(clusterManager); + } + + void _removeClusterManager(ClusterManager clusterManager) { + setState(() { + // Remove markers managed by cluster manager to be removed. + markers.removeWhere((MarkerId key, Marker marker) => + marker.clusterManagerId == clusterManager.clusterManagerId); + // Remove cluster manager. + clusterManagers.remove(clusterManager.clusterManagerId); + }); + } + + void _addMarkersToCluster(ClusterManager clusterManager) { + for (int i = 0; i < _markersToAddToClusterManagerCount; i++) { + final String markerIdVal = + '${clusterManager.clusterManagerId.value}_marker_id_$_markerIdCounter'; + _markerIdCounter++; + final MarkerId markerId = MarkerId(markerIdVal); + + final int clusterManagerIndex = + clusterManagers.values.toList().indexOf(clusterManager); + + // Add additional offset to longitude for each cluster manager to space + // out markers in different cluster managers. + final double clusterManagerLongitudeOffset = + clusterManagerIndex * _clusterManagerLongitudeOffset; + + final Marker marker = Marker( + clusterManagerId: clusterManager.clusterManagerId, + markerId: markerId, + position: LatLng( + center.latitude + _getRandomOffset(), + center.longitude + _getRandomOffset() + clusterManagerLongitudeOffset, + ), + infoWindow: InfoWindow(title: markerIdVal, snippet: '*'), + onTap: () => _onMarkerTapped(markerId), + ); + markers[markerId] = marker; + } + setState(() {}); + } + + double _getRandomOffset() { + return (Random().nextDouble() - 0.5) * _markerOffsetFactor; + } + + void _remove(MarkerId markerId) { + setState(() { + if (markers.containsKey(markerId)) { + markers.remove(markerId); + } + }); + } + + void _changeMarkersAlpha() { + for (final MarkerId markerId in markers.keys) { + final Marker marker = markers[markerId]!; + final double current = marker.alpha; + markers[markerId] = marker.copyWith( + alphaParam: current == _fullyVisibleAlpha + ? _halfVisibleAlpha + : _fullyVisibleAlpha, + ); + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final MarkerId? selectedId = selectedMarker; + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ExampleGoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: initialCameraPosition, + markers: Set.of(markers.values), + clusterManagers: Set.of(clusterManagers.values), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: clusterManagers.length >= _clusterManagerMaxCount + ? null + : () => _addClusterManager(), + child: const Text('Add cluster manager'), + ), + TextButton( + onPressed: clusterManagers.isEmpty + ? null + : () => _removeClusterManager(clusterManagers.values.last), + child: const Text('Remove cluster manager'), + ), + ], + ), + Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + for (final MapEntry clusterEntry + in clusterManagers.entries) + TextButton( + onPressed: () => _addMarkersToCluster(clusterEntry.value), + child: Text('Add markers to ${clusterEntry.key.value}'), + ), + ], + ), + Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: selectedId == null + ? null + : () { + _remove(selectedId); + setState(() { + selectedMarker = null; + }); + }, + child: const Text('Remove selected marker'), + ), + TextButton( + onPressed: markers.isEmpty ? null : () => _changeMarkersAlpha(), + child: const Text('Change all markers alpha'), + ), + ], + ), + if (lastCluster != null) + Padding( + padding: const EdgeInsets.all(10), + child: Text( + 'Cluster with ${lastCluster!.count} markers clicked at ${lastCluster!.position}')), + ], + ); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/custom_marker_icon.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/custom_marker_icon.dart new file mode 100644 index 00000000000..8940762f02e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/custom_marker_icon.dart @@ -0,0 +1,56 @@ +// 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:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +/// Returns a generated png image in [ByteData] format with the requested size. +Future createCustomMarkerIconImage({required Size size}) async { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final _MarkerPainter painter = _MarkerPainter(); + + painter.paint(canvas, size); + + final ui.Image image = await recorder + .endRecording() + .toImage(size.width.floor(), size.height.floor()); + + final ByteData? bytes = + await image.toByteData(format: ui.ImageByteFormat.png); + return bytes!; +} + +class _MarkerPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + const RadialGradient gradient = RadialGradient( + colors: [Colors.yellow, Colors.red], + stops: [0.4, 1.0], + ); + + // Draw radial gradient + canvas.drawRect( + rect, + Paint()..shader = gradient.createShader(rect), + ); + + // Draw diagonal black line + canvas.drawLine( + Offset.zero, + Offset(size.width, size.height), + Paint() + ..color = Colors.black + ..strokeWidth = 1, + ); + } + + @override + bool shouldRepaint(_MarkerPainter oldDelegate) => false; + @override + bool shouldRebuildSemantics(_MarkerPainter oldDelegate) => false; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart index 0734731af7c..fcf24452c87 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart @@ -87,6 +87,9 @@ class ExampleGoogleMapController { .listen((MapTapEvent e) => _googleMapState.onTap(e.position)); GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen( (MapLongPressEvent e) => _googleMapState.onLongPress(e.position)); + GoogleMapsFlutterPlatform.instance + .onClusterTap(mapId: mapId) + .listen((ClusterTapEvent e) => _googleMapState.onClusterTap(e.value)); } /// Updates configuration options of the map user interface. @@ -101,6 +104,13 @@ class ExampleGoogleMapController { .updateMarkers(markerUpdates, mapId: mapId); } + /// Updates cluster manager configuration. + Future _updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates) { + return GoogleMapsFlutterPlatform.instance + .updateClusterManagers(clusterManagerUpdates, mapId: mapId); + } + /// Updates polygon configuration. Future _updatePolygons(PolygonUpdates polygonUpdates) { return GoogleMapsFlutterPlatform.instance @@ -237,6 +247,7 @@ class ExampleGoogleMap extends StatefulWidget { this.polygons = const {}, this.polylines = const {}, this.circles = const {}, + this.clusterManagers = const {}, this.onCameraMoveStarted, this.tileOverlays = const {}, this.onCameraMove, @@ -312,6 +323,9 @@ class ExampleGoogleMap extends StatefulWidget { /// Tile overlays to be placed on the map. final Set tileOverlays; + /// Cluster Managers to be placed for the map. + final Set clusterManagers; + /// Called when the camera starts moving. final VoidCallback? onCameraMoveStarted; @@ -371,6 +385,8 @@ class _ExampleGoogleMapState extends State { Map _polygons = {}; Map _polylines = {}; Map _circles = {}; + Map _clusterManagers = + {}; late MapConfiguration _mapConfiguration; @override @@ -390,6 +406,7 @@ class _ExampleGoogleMapState extends State { polygons: widget.polygons, polylines: widget.polylines, circles: widget.circles, + clusterManagers: widget.clusterManagers, ), mapConfiguration: _mapConfiguration, ); @@ -399,6 +416,7 @@ class _ExampleGoogleMapState extends State { void initState() { super.initState(); _mapConfiguration = _configurationFromMapWidget(widget); + _clusterManagers = keyByClusterManagerId(widget.clusterManagers); _markers = keyByMarkerId(widget.markers); _polygons = keyByPolygonId(widget.polygons); _polylines = keyByPolylineId(widget.polylines); @@ -416,6 +434,7 @@ class _ExampleGoogleMapState extends State { void didUpdateWidget(ExampleGoogleMap oldWidget) { super.didUpdateWidget(oldWidget); _updateOptions(); + _updateClusterManagers(); _updateMarkers(); _updatePolygons(); _updatePolylines(); @@ -441,6 +460,13 @@ class _ExampleGoogleMapState extends State { _markers = keyByMarkerId(widget.markers); } + Future _updateClusterManagers() async { + final ExampleGoogleMapController controller = await _controller.future; + unawaited(controller._updateClusterManagers(ClusterManagerUpdates.from( + _clusterManagers.values.toSet(), widget.clusterManagers))); + _clusterManagers = keyByClusterManagerId(widget.clusterManagers); + } + Future _updatePolygons() async { final ExampleGoogleMapController controller = await _controller.future; unawaited(controller._updatePolygons( @@ -518,6 +544,12 @@ class _ExampleGoogleMapState extends State { void onLongPress(LatLng position) { widget.onLongPress?.call(position); } + + void onClusterTap(Cluster cluster) { + final ClusterManager? clusterManager = + _clusterManagers[cluster.clusterManagerId]; + clusterManager?.onClusterTap?.call(cluster); + } } /// Builds a [MapConfiguration] from the given [map]. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart index 9b9c32aad96..16e1dfd59bb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart @@ -9,6 +9,7 @@ import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'animate_camera.dart'; +import 'clustering.dart'; import 'lite_mode.dart'; import 'map_click.dart'; import 'map_coordinates.dart'; @@ -42,6 +43,7 @@ final List _allPages = [ const SnapshotPage(), const LiteModePage(), const TileOverlayPage(), + const ClusteringPage(), const MapIdPage(), ]; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart index 174055613a9..df4f79205e8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart @@ -5,9 +5,13 @@ // ignore_for_file: public_member_api_docs // ignore_for_file: unawaited_futures +import 'dart:async'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'custom_marker_icon.dart'; import 'example_google_map.dart'; import 'page.dart'; @@ -30,66 +34,303 @@ class MarkerIconsBody extends StatefulWidget { const LatLng _kMapCenter = LatLng(52.4478, -3.5402); +enum _MarkerSizeOption { + original, + width30, + height40, + size30x60, + size120x60, +} + class MarkerIconsBodyState extends State { + final Size _markerAssetImageSize = const Size(48, 48); + _MarkerSizeOption _currentSizeOption = _MarkerSizeOption.original; + Set _markers = {}; + bool _scalingEnabled = true; + bool _mipMapsEnabled = true; ExampleGoogleMapController? controller; - BitmapDescriptor? _markerIcon; + AssetMapBitmap? _markerIconAsset; + BytesMapBitmap? _markerIconBytes; + final int _markersAmountPerType = 15; + bool get _customSizeEnabled => + _currentSizeOption != _MarkerSizeOption.original; @override Widget build(BuildContext context) { - _createMarkerImageFromAsset(context); + _createCustomMarkerIconImages(context); + final Size referenceSize = _getMarkerReferenceSize(); return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Center( - child: SizedBox( - width: 350.0, - height: 300.0, - child: ExampleGoogleMap( - initialCameraPosition: const CameraPosition( - target: _kMapCenter, - zoom: 7.0, + Column(children: [ + Center( + child: SizedBox( + width: 350.0, + height: 300.0, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition( + target: _kMapCenter, + zoom: 7.0, + ), + markers: _markers, + onMapCreated: _onMapCreated, + ), + ), + ), + TextButton( + onPressed: () => _toggleScaling(context), + child: Text(_scalingEnabled + ? 'Disable auto scaling' + : 'Enable auto scaling'), + ), + if (_scalingEnabled) ...[ + Container( + width: referenceSize.width, + height: referenceSize.height, + decoration: BoxDecoration( + border: Border.all(), ), - markers: {_createMarker()}, - onMapCreated: _onMapCreated, ), + Text( + 'Reference box with size of ${referenceSize.width} x ${referenceSize.height} in logical pixels.'), + const SizedBox(height: 10), + Image.asset( + 'assets/red_square.png', + scale: _mipMapsEnabled ? null : 1.0, + ), + const Text('Asset image rendered with flutter'), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Marker size:'), + const SizedBox(width: 10), + DropdownButton<_MarkerSizeOption>( + value: _currentSizeOption, + onChanged: (_MarkerSizeOption? newValue) { + if (newValue != null) { + setState(() { + _currentSizeOption = newValue; + _updateMarkerImages(context); + }); + } + }, + items: + _MarkerSizeOption.values.map((_MarkerSizeOption option) { + return DropdownMenuItem<_MarkerSizeOption>( + value: option, + child: Text(_getMarkerSizeOptionName(option)), + ); + }).toList(), + ) + ], + ), + ], + TextButton( + onPressed: () => _toggleMipMaps(context), + child: Text(_mipMapsEnabled ? 'Disable mipmaps' : 'Enable mipmaps'), ), - ) + ]) ], ); } - Marker _createMarker() { - if (_markerIcon != null) { - return Marker( - markerId: const MarkerId('marker_1'), - position: _kMapCenter, - icon: _markerIcon!, - ); + String _getMarkerSizeOptionName(_MarkerSizeOption option) { + switch (option) { + case _MarkerSizeOption.original: + return 'Original'; + case _MarkerSizeOption.width30: + return 'Width 30'; + case _MarkerSizeOption.height40: + return 'Height 40'; + case _MarkerSizeOption.size30x60: + return '30x60'; + case _MarkerSizeOption.size120x60: + return '120x60'; + } + } + + (double? width, double? height) _getCurrentMarkerSize() { + if (_scalingEnabled) { + switch (_currentSizeOption) { + case _MarkerSizeOption.width30: + return (30, null); + case _MarkerSizeOption.height40: + return (null, 40); + case _MarkerSizeOption.size30x60: + return (30, 60); + case _MarkerSizeOption.size120x60: + return (120, 60); + case _MarkerSizeOption.original: + return (_markerAssetImageSize.width, _markerAssetImageSize.height); + } } else { - return const Marker( - markerId: MarkerId('marker_1'), - position: _kMapCenter, - ); + return (_markerAssetImageSize.width, _markerAssetImageSize.height); } } - Future _createMarkerImageFromAsset(BuildContext context) async { - if (_markerIcon == null) { - final ImageConfiguration imageConfiguration = - createLocalImageConfiguration(context, size: const Size.square(48)); - BitmapDescriptor.fromAssetImage( - imageConfiguration, 'assets/red_square.png') - .then(_updateBitmap); + // Helper method to calculate reference size for custom marker size. + Size _getMarkerReferenceSize() { + final (double? width, double? height) = _getCurrentMarkerSize(); + + // Calculates reference size using _markerAssetImageSize aspect ration: + + if (width != null && height != null) { + return Size(width, height); + } else if (width != null) { + return Size(width, + width * _markerAssetImageSize.height / _markerAssetImageSize.width); + } else if (height != null) { + return Size( + height * _markerAssetImageSize.width / _markerAssetImageSize.height, + height); + } else { + return _markerAssetImageSize; } } - void _updateBitmap(BitmapDescriptor bitmap) { + void _toggleMipMaps(BuildContext context) { + _mipMapsEnabled = !_mipMapsEnabled; + _updateMarkerImages(context); + } + + void _toggleScaling(BuildContext context) { + _scalingEnabled = !_scalingEnabled; + _updateMarkerImages(context); + } + + void _updateMarkerImages(BuildContext context) { + _updateMarkerAssetImage(context); + _updateMarkerBytesImage(context); + _updateMarkers(); + } + + Marker _createAssetMarker(int index) { + final LatLng position = + LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude - 1); + + return Marker( + markerId: MarkerId('marker_asset_$index'), + position: position, + icon: _markerIconAsset!, + ); + } + + Marker _createBytesMarker(int index) { + final LatLng position = + LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude + 1); + + return Marker( + markerId: MarkerId('marker_bytes_$index'), + position: position, + icon: _markerIconBytes!, + ); + } + + void _updateMarkers() { + final Set markers = {}; + for (int i = 0; i < _markersAmountPerType; i++) { + if (_markerIconAsset != null) { + markers.add(_createAssetMarker(i)); + } + if (_markerIconBytes != null) { + markers.add(_createBytesMarker(i)); + } + } setState(() { - _markerIcon = bitmap; + _markers = markers; }); } + Future _updateMarkerAssetImage(BuildContext context) async { + // Width and height are used only for custom size. + final (double? width, double? height) = + _scalingEnabled && _customSizeEnabled + ? _getCurrentMarkerSize() + : (null, null); + + AssetMapBitmap assetMapBitmap; + if (_mipMapsEnabled) { + final ImageConfiguration imageConfiguration = + createLocalImageConfiguration( + context, + ); + + assetMapBitmap = await AssetMapBitmap.create( + imageConfiguration, + 'assets/red_square.png', + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none, + ); + } else { + // Uses hardcoded asset path + // This bypasses the asset resolving logic and allows to load the asset + // with precise path. + assetMapBitmap = AssetMapBitmap( + 'assets/red_square.png', + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none, + ); + } + + _updateAssetBitmap(assetMapBitmap); + } + + Future _updateMarkerBytesImage(BuildContext context) async { + final double? devicePixelRatio = + MediaQuery.maybeDevicePixelRatioOf(context); + + final Size bitmapLogicalSize = _getMarkerReferenceSize(); + final double? imagePixelRatio = _scalingEnabled ? devicePixelRatio : null; + + // Create canvasSize with physical marker size + final Size canvasSize = Size( + bitmapLogicalSize.width * (imagePixelRatio ?? 1.0), + bitmapLogicalSize.height * (imagePixelRatio ?? 1.0)); + + final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize); + + // Width and height are used only for custom size. + final (double? width, double? height) = + _scalingEnabled && _customSizeEnabled + ? _getCurrentMarkerSize() + : (null, null); + + final BytesMapBitmap bitmap = BytesMapBitmap(bytes.buffer.asUint8List(), + imagePixelRatio: imagePixelRatio, + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none); + + _updateBytesBitmap(bitmap); + } + + void _updateAssetBitmap(AssetMapBitmap bitmap) { + _markerIconAsset = bitmap; + _updateMarkers(); + } + + void _updateBytesBitmap(BytesMapBitmap bitmap) { + _markerIconBytes = bitmap; + _updateMarkers(); + } + + void _createCustomMarkerIconImages(BuildContext context) { + if (_markerIconAsset == null) { + _updateMarkerAssetImage(context); + } + + if (_markerIconBytes == null) { + _updateMarkerBytesImage(context); + } + } + void _onMapCreated(ExampleGoogleMapController controllerParam) { setState(() { controller = controllerParam; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart index 9cba4975d40..d475787c92f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart @@ -7,11 +7,11 @@ import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'custom_marker_icon.dart'; import 'example_google_map.dart'; import 'page.dart'; @@ -267,26 +267,10 @@ class PlaceMarkerBodyState extends State { }); } - Future _getAssetIcon(BuildContext context) async { - final Completer bitmapIcon = - Completer(); - final ImageConfiguration config = createLocalImageConfiguration(context); - - const AssetImage('assets/red_square.png') - .resolve(config) - .addListener(ImageStreamListener((ImageInfo image, bool sync) async { - final ByteData? bytes = - await image.image.toByteData(format: ImageByteFormat.png); - if (bytes == null) { - bitmapIcon.completeError(Exception('Unable to encode icon')); - return; - } - final BitmapDescriptor bitmap = - BitmapDescriptor.fromBytes(bytes.buffer.asUint8List()); - bitmapIcon.complete(bitmap); - })); - - return bitmapIcon.future; + Future _getMarkerIcon(BuildContext context) async { + const Size canvasSize = Size(48, 48); + final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize); + return BytesMapBitmap(bytes.buffer.asUint8List()); } @override @@ -383,7 +367,7 @@ class PlaceMarkerBodyState extends State { onPressed: selectedId == null ? null : () { - _getAssetIcon(context).then( + _getMarkerIcon(context).then( (BitmapDescriptor icon) { _setMarkerIcon(selectedId, icon); }, diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart index 659ef87e87f..d008b7d3b97 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart @@ -292,10 +292,10 @@ class PlacePolylineBodyState extends State { child: const Text('change joint type [Android only]'), ), TextButton( - onPressed: isIOS || (selectedId == null) + onPressed: (selectedId == null) ? null : () => _changePattern(selectedId), - child: const Text('change pattern [Android only]'), + child: const Text('change pattern'), ), ], ) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml index 533ca53c291..ddac3d7b231 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the google_maps_flutter plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: cupertino_icons: ^1.0.5 @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart index 22447ba5eca..9ac70ab760f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart @@ -94,6 +94,15 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { await _fakeDelay(); } + @override + Future updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) async { + mapInstances[mapId]?.clusterManagerUpdates.add(clusterManagerUpdates); + await _fakeDelay(); + } + @override Future clearTileCache( TileOverlayId tileOverlayId, { @@ -241,6 +250,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onClusterTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override void dispose({required int mapId}) { disposed = true; @@ -282,6 +296,8 @@ class PlatformMapStateRecorder { this.mapObjects = const MapObjects(), this.mapConfiguration = const MapConfiguration(), }) { + clusterManagerUpdates.add(ClusterManagerUpdates.from( + const {}, mapObjects.clusterManagers)); markerUpdates.add(MarkerUpdates.from(const {}, mapObjects.markers)); polygonUpdates .add(PolygonUpdates.from(const {}, mapObjects.polygons)); @@ -300,4 +316,6 @@ class PlatformMapStateRecorder { final List polylineUpdates = []; final List circleUpdates = []; final List> tileOverlaySets = >[]; + final List clusterManagerUpdates = + []; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart index 4e0cad78e86..abe361566b6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart @@ -3,111 +3,114 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'google_maps_flutter_android.dart'; +import 'messages.g.dart'; + /// An Android of implementation of [GoogleMapsInspectorPlatform]. @visibleForTesting class GoogleMapsInspectorAndroid extends GoogleMapsInspectorPlatform { - /// Creates a method-channel-based inspector instance that gets the channel - /// for a given map ID from [channelProvider]. - GoogleMapsInspectorAndroid(MethodChannel? Function(int mapId) channelProvider) - : _channelProvider = channelProvider; + /// Creates an inspector API instance for a given map ID from + /// [inspectorProvider]. + GoogleMapsInspectorAndroid( + MapsInspectorApi? Function(int mapId) inspectorProvider) + : _inspectorProvider = inspectorProvider; - final MethodChannel? Function(int mapId) _channelProvider; + final MapsInspectorApi? Function(int mapId) _inspectorProvider; @override Future areBuildingsEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isBuildingsEnabled'))!; + return _inspectorProvider(mapId)!.areBuildingsEnabled(); } @override Future areRotateGesturesEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isRotateGesturesEnabled'))!; + return _inspectorProvider(mapId)!.areRotateGesturesEnabled(); } @override Future areScrollGesturesEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isScrollGesturesEnabled'))!; + return _inspectorProvider(mapId)!.areScrollGesturesEnabled(); } @override Future areTiltGesturesEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isTiltGesturesEnabled'))!; + return _inspectorProvider(mapId)!.areTiltGesturesEnabled(); } @override Future areZoomControlsEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isZoomControlsEnabled'))!; + return _inspectorProvider(mapId)!.areZoomControlsEnabled(); } @override Future areZoomGesturesEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isZoomGesturesEnabled'))!; + return _inspectorProvider(mapId)!.areZoomGesturesEnabled(); } @override Future getMinMaxZoomLevels({required int mapId}) async { - final List zoomLevels = (await _channelProvider(mapId)! - .invokeMethod>('map#getMinMaxZoomLevels'))! - .cast(); - return MinMaxZoomPreference(zoomLevels[0], zoomLevels[1]); + final PlatformZoomRange zoomLevels = + await _inspectorProvider(mapId)!.getZoomRange(); + return MinMaxZoomPreference(zoomLevels.min, zoomLevels.max); } @override Future getTileOverlayInfo(TileOverlayId tileOverlayId, {required int mapId}) async { - final Map? tileInfo = await _channelProvider(mapId)! - .invokeMapMethod( - 'map#getTileOverlayInfo', { - 'tileOverlayId': tileOverlayId.value, - }); + final PlatformTileLayer? tileInfo = await _inspectorProvider(mapId)! + .getTileOverlayInfo(tileOverlayId.value); if (tileInfo == null) { return null; } return TileOverlay( tileOverlayId: tileOverlayId, - fadeIn: tileInfo['fadeIn']! as bool, - transparency: tileInfo['transparency']! as double, - visible: tileInfo['visible']! as bool, - // Android and iOS return different types. - zIndex: (tileInfo['zIndex']! as num).toInt(), + fadeIn: tileInfo.fadeIn, + transparency: tileInfo.transparency, + visible: tileInfo.visible, + // The plugin's API only allows setting integer z-index values, so this + // should never actually lose information. + zIndex: tileInfo.zIndex.round(), ); } @override Future isCompassEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isCompassEnabled'))!; + return _inspectorProvider(mapId)!.isCompassEnabled(); } @override Future isLiteModeEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isLiteModeEnabled'))!; + // Null indicates "unspecified"; interpret that as not enabled. + return (await _inspectorProvider(mapId)!.isLiteModeEnabled()) ?? false; } @override Future isMapToolbarEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isMapToolbarEnabled'))!; + return _inspectorProvider(mapId)!.isMapToolbarEnabled(); } @override Future isMyLocationButtonEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isMyLocationButtonEnabled'))!; + return _inspectorProvider(mapId)!.isMyLocationButtonEnabled(); } @override Future isTrafficEnabled({required int mapId}) async { - return (await _channelProvider(mapId)! - .invokeMethod('map#isTrafficEnabled'))!; + return _inspectorProvider(mapId)!.isTrafficEnabled(); + } + + @override + Future> getClusters({ + required int mapId, + required ClusterManagerId clusterManagerId, + }) async { + return (await _inspectorProvider(mapId)! + .getClusters(clusterManagerId.value)) + // See comment in messages.dart for why the force unwrap is okay. + .map((PlatformCluster? cluster) => + GoogleMapsFlutterAndroid.clusterFromPlatformCluster(cluster!)) + .toList(); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index d40d795d6a3..3b77fa91480 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -13,10 +13,17 @@ import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf import 'package:stream_transform/stream_transform.dart'; import 'google_map_inspector_android.dart'; +import 'messages.g.dart'; +import 'utils/cluster_manager_utils.dart'; // TODO(stuartmorgan): Remove the dependency on platform interface toJson // methods. Channel serialization details should all be package-internal. +/// The non-test implementation of `_apiProvider`. +MapsApi _productionApiProvider(int mapId) { + return MapsApi(messageChannelSuffix: mapId.toString()); +} + /// Error thrown when an unknown map ID is provided to a method channel API. class UnknownMapIDError extends Error { /// Creates an assertion error with the provided [mapId] and optional @@ -53,26 +60,32 @@ enum AndroidMapRenderer { /// An implementation of [GoogleMapsFlutterPlatform] for Android. class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { + /// Creates a new Android maps implementation instance. + GoogleMapsFlutterAndroid({ + @visibleForTesting MapsApi Function(int mapId)? apiProvider, + }) : _apiProvider = apiProvider ?? _productionApiProvider; + /// Registers the Android implementation of GoogleMapsFlutterPlatform. static void registerWith() { GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterAndroid(); } - /// The method channel used to initialize the native Google Maps SDK. - final MethodChannel _initializerChannel = const MethodChannel( - 'plugins.flutter.dev/google_maps_android_initializer'); - // Keep a collection of id -> channel // Every method call passes the int mapId final Map _channels = {}; - /// Accesses the MethodChannel associated to the passed mapId. - MethodChannel _channel(int mapId) { - final MethodChannel? channel = _channels[mapId]; - if (channel == null) { + final Map _hostMaps = {}; + + // A method to create MapsApi instances, which can be overridden for testing. + final MapsApi Function(int mapId) _apiProvider; + + /// Accesses the MapsApi associated to the passed mapId. + MapsApi _hostApi(int mapId) { + final MapsApi? api = _hostMaps[mapId]; + if (api == null) { throw UnknownMapIDError(mapId); } - return channel; + return api; } // Keep a collection of mapId to a map of TileOverlays. @@ -92,10 +105,23 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return channel; } + /// Returns the API instance for [mapId], creating it if it doesn't already + /// exist. + @visibleForTesting + MapsApi ensureApiInitialized(int mapId) { + MapsApi? api = _hostMaps[mapId]; + if (api == null) { + api = _apiProvider(mapId); + _hostMaps[mapId] ??= api; + } + return api; + } + @override Future init(int mapId) { - final MethodChannel channel = ensureChannelInitialized(mapId); - return channel.invokeMethod('map#waitForMap'); + ensureChannelInitialized(mapId); + final MapsApi hostApi = ensureApiInitialized(mapId); + return hostApi.waitForMap(); } @override @@ -181,6 +207,11 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onClusterTap({required int mapId}) { + return _events(mapId).whereType(); + } + Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { case 'camera#onMoveStarted': @@ -273,6 +304,17 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { arguments['zoom'] as int?, ); return tile.toJson(); + case 'cluster#onTap': + final Map arguments = _getArgumentDictionary(call); + final Cluster cluster = parseCluster( + arguments['clusterManagerId']! as String, + arguments['position']!, + arguments['bounds']! as Map, + arguments['markerIds']! as List); + _mapEventStreamController.add(ClusterTapEvent( + mapId, + cluster, + )); default: throw MissingPluginException(); } @@ -286,17 +328,22 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return (call.arguments as Map).cast(); } + @override + Future updateMapConfiguration( + MapConfiguration configuration, { + required int mapId, + }) { + return updateMapOptions(_jsonForMapConfiguration(configuration), + mapId: mapId); + } + @override Future updateMapOptions( Map optionsUpdate, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'map#update', - { - 'options': optionsUpdate, - }, - ); + return _hostApi(mapId) + .updateMapConfiguration(PlatformMapConfiguration(json: optionsUpdate)); } @override @@ -304,9 +351,10 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MarkerUpdates markerUpdates, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'markers#update', - markerUpdates.toJson(), + return _hostApi(mapId).updateMarkers( + markerUpdates.markersToAdd.map(_platformMarkerFromMarker).toList(), + markerUpdates.markersToChange.map(_platformMarkerFromMarker).toList(), + markerUpdates.markerIdsToRemove.map((MarkerId id) => id.value).toList(), ); } @@ -315,9 +363,12 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { PolygonUpdates polygonUpdates, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'polygons#update', - polygonUpdates.toJson(), + return _hostApi(mapId).updatePolygons( + polygonUpdates.polygonsToAdd.map(_platformPolygonFromPolygon).toList(), + polygonUpdates.polygonsToChange.map(_platformPolygonFromPolygon).toList(), + polygonUpdates.polygonIdsToRemove + .map((PolygonId id) => id.value) + .toList(), ); } @@ -326,9 +377,16 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { PolylineUpdates polylineUpdates, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'polylines#update', - polylineUpdates.toJson(), + return _hostApi(mapId).updatePolylines( + polylineUpdates.polylinesToAdd + .map(_platformPolylineFromPolyline) + .toList(), + polylineUpdates.polylinesToChange + .map(_platformPolylineFromPolyline) + .toList(), + polylineUpdates.polylineIdsToRemove + .map((PolylineId id) => id.value) + .toList(), ); } @@ -337,9 +395,10 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { CircleUpdates circleUpdates, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'circles#update', - circleUpdates.toJson(), + return _hostApi(mapId).updateCircles( + circleUpdates.circlesToAdd.map(_platformCircleFromCircle).toList(), + circleUpdates.circlesToChange.map(_platformCircleFromCircle).toList(), + circleUpdates.circleIdsToRemove.map((CircleId id) => id.value).toList(), ); } @@ -356,9 +415,31 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { final _TileOverlayUpdates updates = _TileOverlayUpdates.from(previousSet, newTileOverlays); _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays); - return _channel(mapId).invokeMethod( - 'tileOverlays#update', - updates.toJson(), + return _hostApi(mapId).updateTileOverlays( + updates.tileOverlaysToAdd + .map(_platformTileOverlayFromTileOverlay) + .toList(), + updates.tileOverlaysToChange + .map(_platformTileOverlayFromTileOverlay) + .toList(), + updates.tileOverlayIdsToRemove + .map((TileOverlayId id) => id.value) + .toList(), + ); + } + + @override + Future updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) { + return _hostApi(mapId).updateClusterManagers( + clusterManagerUpdates.clusterManagersToAdd + .map(_platformClusterManagerFromClusterManager) + .toList(), + clusterManagerUpdates.clusterManagerIdsToRemove + .map((ClusterManagerId id) => id.value) + .toList(), ); } @@ -367,10 +448,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { TileOverlayId tileOverlayId, { required int mapId, }) { - return _channel(mapId) - .invokeMethod('tileOverlays#clearTileCache', { - 'tileOverlayId': tileOverlayId.value, - }); + return _hostApi(mapId).clearTileCache(tileOverlayId.value); } @override @@ -378,10 +456,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { CameraUpdate cameraUpdate, { required int mapId, }) { - return _channel(mapId) - .invokeMethod('camera#animate', { - 'cameraUpdate': cameraUpdate.toJson(), - }); + return _hostApi(mapId) + .animateCamera(PlatformCameraUpdate(json: cameraUpdate.toJson())); } @override @@ -389,9 +465,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { CameraUpdate cameraUpdate, { required int mapId, }) { - return _channel(mapId).invokeMethod('camera#move', { - 'cameraUpdate': cameraUpdate.toJson(), - }); + return _hostApi(mapId) + .moveCamera(PlatformCameraUpdate(json: cameraUpdate.toJson())); } @override @@ -399,11 +474,9 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { String? mapStyle, { required int mapId, }) async { - final List successAndError = (await _channel(mapId) - .invokeMethod>('map#setStyle', mapStyle))!; - final bool success = successAndError[0] as bool; + final bool success = await _hostApi(mapId).setStyle(mapStyle ?? ''); if (!success) { - throw MapStyleException(successAndError[1] as String); + throw const MapStyleException(_setStyleFailureMessage); } } @@ -411,12 +484,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { Future getVisibleRegion({ required int mapId, }) async { - final Map latLngBounds = (await _channel(mapId) - .invokeMapMethod('map#getVisibleRegion'))!; - final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!; - final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!; - - return LatLngBounds(northeast: northeast, southwest: southwest); + return _latLngBoundsFromPlatformLatLngBounds( + await _hostApi(mapId).getVisibleRegion()); } @override @@ -424,11 +493,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { LatLng latLng, { required int mapId, }) async { - final Map point = (await _channel(mapId) - .invokeMapMethod( - 'map#getScreenCoordinate', latLng.toJson()))!; - - return ScreenCoordinate(x: point['x']!, y: point['y']!); + return _screenCoordinateFromPlatformPoint(await _hostApi(mapId) + .getScreenCoordinate(_platformLatLngFromLatLng(latLng))); } @override @@ -436,10 +502,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { ScreenCoordinate screenCoordinate, { required int mapId, }) async { - final List latLng = (await _channel(mapId) - .invokeMethod>( - 'map#getLatLng', screenCoordinate.toJson()))!; - return LatLng(latLng[0] as double, latLng[1] as double); + return _latLngFromPlatformLatLng(await _hostApi(mapId) + .getLatLng(_platformPointFromScreenCoordinate(screenCoordinate))); } @override @@ -447,8 +511,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MarkerId markerId, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'markers#showInfoWindow', {'markerId': markerId.value}); + return _hostApi(mapId).showInfoWindow(markerId.value); } @override @@ -456,37 +519,36 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MarkerId markerId, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'markers#hideInfoWindow', {'markerId': markerId.value}); + return _hostApi(mapId).hideInfoWindow(markerId.value); } @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, - }) async { - return (await _channel(mapId).invokeMethod( - 'markers#isInfoWindowShown', - {'markerId': markerId.value}))!; + }) { + return _hostApi(mapId).isInfoWindowShown(markerId.value); } @override Future getZoomLevel({ required int mapId, - }) async { - return (await _channel(mapId).invokeMethod('map#getZoomLevel'))!; + }) { + return _hostApi(mapId).getZoomLevel(); } @override Future takeSnapshot({ required int mapId, }) { - return _channel(mapId).invokeMethod('map#takeSnapshot'); + return _hostApi(mapId).takeSnapshot(); } @override - Future getStyleError({required int mapId}) { - return _channel(mapId).invokeMethod('map#getStyleError'); + Future getStyleError({required int mapId}) async { + return (await _hostApi(mapId).didLastStyleSucceed()) + ? null + : _setStyleFailureMessage; } /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the @@ -511,35 +573,25 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { /// Initialized [AndroidMapRenderer] type is returned. Future initializeWithRenderer( AndroidMapRenderer? rendererType) async { - String preferredRenderer; + PlatformRendererType? preferredRenderer; switch (rendererType) { case AndroidMapRenderer.latest: - preferredRenderer = 'latest'; + preferredRenderer = PlatformRendererType.latest; case AndroidMapRenderer.legacy: - preferredRenderer = 'legacy'; + preferredRenderer = PlatformRendererType.legacy; case AndroidMapRenderer.platformDefault: case null: - preferredRenderer = 'default'; + preferredRenderer = null; } - final String? initializedRenderer = await _initializerChannel - .invokeMethod('initializer#preferRenderer', - {'value': preferredRenderer}); + final MapsInitializerApi hostApi = MapsInitializerApi(); + final PlatformRendererType initializedRenderer = + await hostApi.initializeWithPreferredRenderer(preferredRenderer); - if (initializedRenderer == null) { - throw AndroidMapRendererException('Failed to initialize map renderer.'); - } - - // Returns mapped [AndroidMapRenderer] enum type. - switch (initializedRenderer) { - case 'latest': - return AndroidMapRenderer.latest; - case 'legacy': - return AndroidMapRenderer.legacy; - default: - throw AndroidMapRendererException( - 'Failed to initialize latest or legacy renderer, got $initializedRenderer.'); - } + return switch (initializedRenderer) { + PlatformRendererType.latest => AndroidMapRenderer.latest, + PlatformRendererType.legacy => AndroidMapRenderer.legacy, + }; } Widget _buildView( @@ -549,6 +601,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MapObjects mapObjects = const MapObjects(), Map mapOptions = const {}, }) { + // TODO(stuartmorgan): Convert this to Pigeon-generated structures once + // https://github.com/flutter/flutter/issues/150631 is fixed. final Map creationParams = { 'initialCameraPosition': widgetConfiguration.initialCameraPosition.toMap(), @@ -558,6 +612,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { 'polylinesToAdd': serializePolylineSet(mapObjects.polylines), 'circlesToAdd': serializeCircleSet(mapObjects.circles), 'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays), + 'clusterManagersToAdd': + serializeClusterManagerSet(mapObjects.clusterManagers), }; const String viewType = 'plugins.flutter.dev/google_maps_android'; @@ -635,6 +691,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { @@ -649,6 +706,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { polygons: polygons, polylines: polylines, circles: circles, + clusterManagers: clusterManagers, tileOverlays: tileOverlays), mapOptions: mapOptions, ); @@ -664,6 +722,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { @@ -677,6 +736,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { polylines: polylines, circles: circles, tileOverlays: tileOverlays, + clusterManagers: clusterManagers, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); @@ -685,8 +745,106 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { @override @visibleForTesting void enableDebugInspection() { - GoogleMapsInspectorPlatform.instance = - GoogleMapsInspectorAndroid((int mapId) => _channel(mapId)); + GoogleMapsInspectorPlatform.instance = GoogleMapsInspectorAndroid( + (int mapId) => + MapsInspectorApi(messageChannelSuffix: mapId.toString())); + } + + /// Parses cluster data from dynamic json objects and returns [Cluster] object. + /// Used by the `cluster#onTap` method call handler and the + /// [GoogleMapsInspectorAndroid.getClusters] response parser. + static Cluster parseCluster( + String clusterManagerIdString, + Object positionObject, + Map boundsMap, + List markerIdsList) { + final ClusterManagerId clusterManagerId = + ClusterManagerId(clusterManagerIdString); + final LatLng position = LatLng.fromJson(positionObject)!; + + final Map> latLngData = boundsMap.map( + (dynamic key, dynamic object) => MapEntry>( + key as String, object as List)); + + final LatLngBounds bounds = LatLngBounds( + northeast: LatLng.fromJson(latLngData['northeast'])!, + southwest: LatLng.fromJson(latLngData['southwest'])!); + + final List markerIds = markerIdsList + .map((dynamic markerId) => MarkerId(markerId as String)) + .toList(); + + return Cluster( + clusterManagerId, + markerIds, + position: position, + bounds: bounds, + ); + } + + /// Converts a Pigeon [PlatformCluster] to the corresponding [Cluster]. + static Cluster clusterFromPlatformCluster(PlatformCluster cluster) { + return Cluster( + ClusterManagerId(cluster.clusterManagerId), + cluster.markerIds + // See comment in messages.dart for why the force unwrap is okay. + .map((String? markerId) => MarkerId(markerId!)) + .toList(), + position: _latLngFromPlatformLatLng(cluster.position), + bounds: _latLngBoundsFromPlatformLatLngBounds(cluster.bounds)); + } + + static LatLng _latLngFromPlatformLatLng(PlatformLatLng latLng) { + return LatLng(latLng.latitude, latLng.longitude); + } + + static PlatformLatLng _platformLatLngFromLatLng(LatLng latLng) { + return PlatformLatLng( + latitude: latLng.latitude, longitude: latLng.longitude); + } + + static ScreenCoordinate _screenCoordinateFromPlatformPoint( + PlatformPoint point) { + return ScreenCoordinate(x: point.x, y: point.y); + } + + static PlatformPoint _platformPointFromScreenCoordinate( + ScreenCoordinate coordinate) { + return PlatformPoint(x: coordinate.x, y: coordinate.y); + } + + static LatLngBounds _latLngBoundsFromPlatformLatLngBounds( + PlatformLatLngBounds bounds) { + return LatLngBounds( + southwest: _latLngFromPlatformLatLng(bounds.southwest), + northeast: _latLngFromPlatformLatLng(bounds.northeast)); + } + + static PlatformCircle _platformCircleFromCircle(Circle circle) { + return PlatformCircle(json: circle.toJson()); + } + + static PlatformClusterManager _platformClusterManagerFromClusterManager( + ClusterManager clusterManager) { + return PlatformClusterManager( + identifier: clusterManager.clusterManagerId.value); + } + + static PlatformMarker _platformMarkerFromMarker(Marker marker) { + return PlatformMarker(json: marker.toJson()); + } + + static PlatformPolygon _platformPolygonFromPolygon(Polygon polygon) { + return PlatformPolygon(json: polygon.toJson()); + } + + static PlatformPolyline _platformPolylineFromPolyline(Polyline polyline) { + return PlatformPolyline(json: polyline.toJson()); + } + + static PlatformTileOverlay _platformTileOverlayFromTileOverlay( + TileOverlay tileOverlay) { + return PlatformTileOverlay(json: tileOverlay.toJson()); } } @@ -767,3 +925,9 @@ class AndroidMapRendererException implements Exception { @override String toString() => 'AndroidMapRendererException($message)'; } + +/// The error message to use for style failures. Unlike iOS, Android does not +/// provide an API to get style failure information, it's just logged to the +/// console, so there's no platform call needed. +const String _setStyleFailureMessage = + 'Unable to set the map style. Please check console logs for errors.'; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart new file mode 100644 index 00000000000..23f7aa45c0a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -0,0 +1,1567 @@ +// 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 (v20.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, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +enum PlatformRendererType { + legacy, + latest, +} + +/// Pigeon representation of a CameraUpdate. +class PlatformCameraUpdate { + PlatformCameraUpdate({ + required this.json, + }); + + /// The update data, as JSON. This should only be set from + /// CameraUpdate.toJson, and the native code must intepret it according to the + /// internal implementation details of the CameraUpdate class. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformCameraUpdate decode(Object result) { + result as List; + return PlatformCameraUpdate( + json: result[0]!, + ); + } +} + +/// Pigeon equivalent of the Circle class. +class PlatformCircle { + PlatformCircle({ + required this.json, + }); + + /// The circle data, as JSON. This should only be set from + /// Circle.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformCircle decode(Object result) { + result as List; + return PlatformCircle( + json: result[0]!, + ); + } +} + +/// Pigeon equivalent of the ClusterManager class. +class PlatformClusterManager { + PlatformClusterManager({ + required this.identifier, + }); + + String identifier; + + Object encode() { + return [ + identifier, + ]; + } + + static PlatformClusterManager decode(Object result) { + result as List; + return PlatformClusterManager( + identifier: result[0]! as String, + ); + } +} + +/// Pigeon equivalent of the Marker class. +class PlatformMarker { + PlatformMarker({ + required this.json, + }); + + /// The marker data, as JSON. This should only be set from + /// Marker.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformMarker decode(Object result) { + result as List; + return PlatformMarker( + json: result[0]!, + ); + } +} + +/// Pigeon equivalent of the Polygon class. +class PlatformPolygon { + PlatformPolygon({ + required this.json, + }); + + /// The polygon data, as JSON. This should only be set from + /// Polygon.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformPolygon decode(Object result) { + result as List; + return PlatformPolygon( + json: result[0]!, + ); + } +} + +/// Pigeon equivalent of the Polyline class. +class PlatformPolyline { + PlatformPolyline({ + required this.json, + }); + + /// The polyline data, as JSON. This should only be set from + /// Polyline.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformPolyline decode(Object result) { + result as List; + return PlatformPolyline( + json: result[0]!, + ); + } +} + +/// Pigeon equivalent of the TileOverlay class. +class PlatformTileOverlay { + PlatformTileOverlay({ + required this.json, + }); + + /// The tile overlay data, as JSON. This should only be set from + /// TileOverlay.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformTileOverlay decode(Object result) { + result as List; + return PlatformTileOverlay( + json: result[0]!, + ); + } +} + +/// Pigeon equivalent of LatLng. +class PlatformLatLng { + PlatformLatLng({ + required this.latitude, + required this.longitude, + }); + + double latitude; + + double longitude; + + Object encode() { + return [ + latitude, + longitude, + ]; + } + + static PlatformLatLng decode(Object result) { + result as List; + return PlatformLatLng( + latitude: result[0]! as double, + longitude: result[1]! as double, + ); + } +} + +/// Pigeon equivalent of LatLngBounds. +class PlatformLatLngBounds { + PlatformLatLngBounds({ + required this.northeast, + required this.southwest, + }); + + PlatformLatLng northeast; + + PlatformLatLng southwest; + + Object encode() { + return [ + northeast, + southwest, + ]; + } + + static PlatformLatLngBounds decode(Object result) { + result as List; + return PlatformLatLngBounds( + northeast: result[0]! as PlatformLatLng, + southwest: result[1]! as PlatformLatLng, + ); + } +} + +/// Pigeon equivalent of Cluster. +class PlatformCluster { + PlatformCluster({ + required this.clusterManagerId, + required this.position, + required this.bounds, + required this.markerIds, + }); + + String clusterManagerId; + + PlatformLatLng position; + + PlatformLatLngBounds bounds; + + List markerIds; + + Object encode() { + return [ + clusterManagerId, + position, + bounds, + markerIds, + ]; + } + + static PlatformCluster decode(Object result) { + result as List; + return PlatformCluster( + clusterManagerId: result[0]! as String, + position: result[1]! as PlatformLatLng, + bounds: result[2]! as PlatformLatLngBounds, + markerIds: (result[3] as List?)!.cast(), + ); + } +} + +/// Pigeon equivalent of MapConfiguration. +class PlatformMapConfiguration { + PlatformMapConfiguration({ + required this.json, + }); + + /// The configuration options, as JSON. This should only be set from + /// _jsonForMapConfiguration, and the native code must intepret it according + /// to the internal implementation details of that method. + Object json; + + Object encode() { + return [ + json, + ]; + } + + static PlatformMapConfiguration decode(Object result) { + result as List; + return PlatformMapConfiguration( + json: result[0]!, + ); + } +} + +/// Pigeon representation of an x,y coordinate. +class PlatformPoint { + PlatformPoint({ + required this.x, + required this.y, + }); + + int x; + + int y; + + Object encode() { + return [ + x, + y, + ]; + } + + static PlatformPoint decode(Object result) { + result as List; + return PlatformPoint( + x: result[0]! as int, + y: result[1]! as int, + ); + } +} + +/// Pigeon equivalent of native TileOverlay properties. +class PlatformTileLayer { + PlatformTileLayer({ + required this.visible, + required this.fadeIn, + required this.transparency, + required this.zIndex, + }); + + bool visible; + + bool fadeIn; + + double transparency; + + double zIndex; + + Object encode() { + return [ + visible, + fadeIn, + transparency, + zIndex, + ]; + } + + static PlatformTileLayer decode(Object result) { + result as List; + return PlatformTileLayer( + visible: result[0]! as bool, + fadeIn: result[1]! as bool, + transparency: result[2]! as double, + zIndex: result[3]! as double, + ); + } +} + +/// Possible outcomes of launching a URL. +class PlatformZoomRange { + PlatformZoomRange({ + required this.min, + required this.max, + }); + + double min; + + double max; + + Object encode() { + return [ + min, + max, + ]; + } + + static PlatformZoomRange decode(Object result) { + result as List; + return PlatformZoomRange( + min: result[0]! as double, + max: result[1]! as double, + ); + } +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is PlatformCameraUpdate) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is PlatformCircle) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is PlatformClusterManager) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is PlatformMarker) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else if (value is PlatformPolygon) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is PlatformPolyline) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is PlatformTileOverlay) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is PlatformLatLng) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is PlatformLatLngBounds) { + buffer.putUint8(137); + writeValue(buffer, value.encode()); + } else if (value is PlatformCluster) { + buffer.putUint8(138); + writeValue(buffer, value.encode()); + } else if (value is PlatformMapConfiguration) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); + } else if (value is PlatformPoint) { + buffer.putUint8(140); + writeValue(buffer, value.encode()); + } else if (value is PlatformTileLayer) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is PlatformZoomRange) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); + } else if (value is PlatformRendererType) { + buffer.putUint8(143); + writeValue(buffer, value.index); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return PlatformCameraUpdate.decode(readValue(buffer)!); + case 130: + return PlatformCircle.decode(readValue(buffer)!); + case 131: + return PlatformClusterManager.decode(readValue(buffer)!); + case 132: + return PlatformMarker.decode(readValue(buffer)!); + case 133: + return PlatformPolygon.decode(readValue(buffer)!); + case 134: + return PlatformPolyline.decode(readValue(buffer)!); + case 135: + return PlatformTileOverlay.decode(readValue(buffer)!); + case 136: + return PlatformLatLng.decode(readValue(buffer)!); + case 137: + return PlatformLatLngBounds.decode(readValue(buffer)!); + case 138: + return PlatformCluster.decode(readValue(buffer)!); + case 139: + return PlatformMapConfiguration.decode(readValue(buffer)!); + case 140: + return PlatformPoint.decode(readValue(buffer)!); + case 141: + return PlatformTileLayer.decode(readValue(buffer)!); + case 142: + return PlatformZoomRange.decode(readValue(buffer)!); + case 143: + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformRendererType.values[value]; + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Interface for non-test interactions with the native SDK. +/// +/// For test-only state queries, see [MapsInspectorApi]. +class MapsApi { + /// Constructor for [MapsApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + MapsApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String __pigeon_messageChannelSuffix; + + /// Returns once the map instance is available. + Future waitForMap() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.waitForMap$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the map's configuration options. + /// + /// Only non-null configuration values will result in updates; options with + /// null values will remain unchanged. + Future updateMapConfiguration( + PlatformMapConfiguration configuration) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateMapConfiguration$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([configuration]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the set of circles on the map. + Future updateCircles(List toAdd, + List toChange, List idsToRemove) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateCircles$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([toAdd, toChange, idsToRemove]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the set of custer managers for clusters on the map. + Future updateClusterManagers( + List toAdd, List idsToRemove) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateClusterManagers$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([toAdd, idsToRemove]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the set of markers on the map. + Future updateMarkers(List toAdd, + List toChange, List idsToRemove) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateMarkers$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([toAdd, toChange, idsToRemove]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the set of polygonss on the map. + Future updatePolygons(List toAdd, + List toChange, List idsToRemove) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updatePolygons$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([toAdd, toChange, idsToRemove]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the set of polylines on the map. + Future updatePolylines(List toAdd, + List toChange, List idsToRemove) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updatePolylines$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([toAdd, toChange, idsToRemove]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Updates the set of tile overlays on the map. + Future updateTileOverlays(List toAdd, + List toChange, List idsToRemove) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.updateTileOverlays$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([toAdd, toChange, idsToRemove]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Gets the screen coordinate for the given map location. + Future getScreenCoordinate(PlatformLatLng latLng) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getScreenCoordinate$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([latLng]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformPoint?)!; + } + } + + /// Gets the map location for the given screen coordinate. + Future getLatLng(PlatformPoint screenCoordinate) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getLatLng$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([screenCoordinate]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformLatLng?)!; + } + } + + /// Gets the map region currently displayed on the map. + Future getVisibleRegion() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getVisibleRegion$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformLatLngBounds?)!; + } + } + + /// Moves the camera according to [cameraUpdate] immediately, with no + /// animation. + Future moveCamera(PlatformCameraUpdate cameraUpdate) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.moveCamera$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([cameraUpdate]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Moves the camera according to [cameraUpdate], animating the update. + Future animateCamera(PlatformCameraUpdate cameraUpdate) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.animateCamera$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([cameraUpdate]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Gets the current map zoom level. + Future getZoomLevel() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getZoomLevel$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + + /// Show the info window for the marker with the given ID. + Future showInfoWindow(String markerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.showInfoWindow$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([markerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Hide the info window for the marker with the given ID. + Future hideInfoWindow(String markerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.hideInfoWindow$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([markerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns true if the marker with the given ID is currently displaying its + /// info window. + Future isInfoWindowShown(String markerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.isInfoWindowShown$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([markerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Sets the style to the given map style string, where an empty string + /// indicates that the style should be cleared. + /// + /// Returns false if there was an error setting the style, such as an invalid + /// style string. + Future setStyle(String style) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.setStyle$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([style]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Returns true if the last attempt to set a style, either via initial map + /// style or setMapStyle, succeeded. + /// + /// This allows checking asynchronously for initial style failures, as there + /// is no way to return failures from map initialization. + Future didLastStyleSucceed() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.didLastStyleSucceed$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Clears the cache of tiles previously requseted from the tile provider. + Future clearTileCache(String tileOverlayId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.clearTileCache$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([tileOverlayId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Takes a snapshot of the map and returns its image data. + Future takeSnapshot() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.takeSnapshot$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Uint8List?)!; + } + } +} + +/// Interface for global SDK initialization. +class MapsInitializerApi { + /// Constructor for [MapsInitializerApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + MapsInitializerApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String __pigeon_messageChannelSuffix; + + /// Initializes the Google Maps SDK with the given renderer preference. + /// + /// A null renderer preference will result in the default renderer. + /// + /// Calling this more than once in the lifetime of an application will result + /// in an error. + Future initializeWithPreferredRenderer( + PlatformRendererType? type) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInitializerApi.initializeWithPreferredRenderer$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([type]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformRendererType?)!; + } + } +} + +/// Inspector API only intended for use in integration tests. +class MapsInspectorApi { + /// Constructor for [MapsInspectorApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + MapsInspectorApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String __pigeon_messageChannelSuffix; + + Future areBuildingsEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areBuildingsEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future areRotateGesturesEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areRotateGesturesEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future areZoomControlsEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areZoomControlsEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future areScrollGesturesEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areScrollGesturesEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future areTiltGesturesEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areTiltGesturesEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future areZoomGesturesEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.areZoomGesturesEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future isCompassEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.isCompassEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future isLiteModeEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.isLiteModeEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as bool?); + } + } + + Future isMapToolbarEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.isMapToolbarEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future isMyLocationButtonEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.isMyLocationButtonEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future isTrafficEnabled() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.isTrafficEnabled$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future getTileOverlayInfo(String tileOverlayId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.getTileOverlayInfo$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([tileOverlayId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return (__pigeon_replyList[0] as PlatformTileLayer?); + } + } + + Future getZoomRange() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.getZoomRange$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformZoomRange?)!; + } + } + + Future> getClusters(String clusterManagerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsInspectorApi.getClusters$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([clusterManagerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)! + .cast(); + } + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/utils/cluster_manager_utils.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/utils/cluster_manager_utils.dart new file mode 100644 index 00000000000..1aab89354a0 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/utils/cluster_manager_utils.dart @@ -0,0 +1,19 @@ +// 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +/// Converts a Set of Cluster Managers into object serializable in JSON. +Object serializeClusterManagerSet(Set clusterManagers) { + return clusterManagers + .map((ClusterManager cm) => _serializeClusterManager(cm)) + .toList(); +} + +/// Converts a Cluster Manager into object serializable in JSON. +Object _serializeClusterManager(ClusterManager clusterManager) { + final Map json = {}; + json['clusterManagerId'] = clusterManager.clusterManagerId.value; + return json; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/copyright.txt b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/copyright.txt new file mode 100644 index 00000000000..1236b63caf3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart new file mode 100644 index 00000000000..3b78a5b180b --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -0,0 +1,318 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + javaOptions: JavaOptions(package: 'io.flutter.plugins.googlemaps'), + javaOut: 'android/src/main/java/io/flutter/plugins/googlemaps/Messages.java', + copyrightHeader: 'pigeons/copyright.txt', +)) + +// Pigeon equivalent of the Java MapsInitializer.Renderer. +enum PlatformRendererType { legacy, latest } + +/// Pigeon representation of a CameraUpdate. +class PlatformCameraUpdate { + PlatformCameraUpdate(this.json); + + /// The update data, as JSON. This should only be set from + /// CameraUpdate.toJson, and the native code must intepret it according to the + /// internal implementation details of the CameraUpdate class. + // TODO(stuartmorgan): Update the google_maps_platform_interface CameraUpdate + // class to provide a structured representation of an update. Currently it + // uses JSON as its only state, so there is no way to preserve structure. + // This wrapper class exists as a placeholder for now to at least provide + // type safety in the top-level call's arguments. + final Object json; +} + +/// Pigeon equivalent of the Circle class. +class PlatformCircle { + PlatformCircle(this.json); + + /// The circle data, as JSON. This should only be set from + /// Circle.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + // TODO(stuartmorgan): Replace this with structured data. This exists only to + // allow incremental migration to Pigeon. + final Object json; +} + +/// Pigeon equivalent of the ClusterManager class. +class PlatformClusterManager { + PlatformClusterManager({required this.identifier}); + + final String identifier; +} + +/// Pigeon equivalent of the Marker class. +class PlatformMarker { + PlatformMarker(this.json); + + /// The marker data, as JSON. This should only be set from + /// Marker.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + // TODO(stuartmorgan): Replace this with structured data. This exists only to + // allow incremental migration to Pigeon. + final Object json; +} + +/// Pigeon equivalent of the Polygon class. +class PlatformPolygon { + PlatformPolygon(this.json); + + /// The polygon data, as JSON. This should only be set from + /// Polygon.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + // TODO(stuartmorgan): Replace this with structured data. This exists only to + // allow incremental migration to Pigeon. + final Object json; +} + +/// Pigeon equivalent of the Polyline class. +class PlatformPolyline { + PlatformPolyline(this.json); + + /// The polyline data, as JSON. This should only be set from + /// Polyline.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + // TODO(stuartmorgan): Replace this with structured data. This exists only to + // allow incremental migration to Pigeon. + final Object json; +} + +/// Pigeon equivalent of the TileOverlay class. +class PlatformTileOverlay { + PlatformTileOverlay(this.json); + + /// The tile overlay data, as JSON. This should only be set from + /// TileOverlay.toJson, and the native code must intepret it according to the + /// internal implementation details of that method. + // TODO(stuartmorgan): Replace this with structured data. This exists only to + // allow incremental migration to Pigeon. + final Object json; +} + +/// Pigeon equivalent of LatLng. +class PlatformLatLng { + PlatformLatLng({required this.latitude, required this.longitude}); + + final double latitude; + final double longitude; +} + +/// Pigeon equivalent of LatLngBounds. +class PlatformLatLngBounds { + PlatformLatLngBounds({required this.northeast, required this.southwest}); + + final PlatformLatLng northeast; + final PlatformLatLng southwest; +} + +/// Pigeon equivalent of Cluster. +class PlatformCluster { + PlatformCluster({ + required this.clusterManagerId, + required this.position, + required this.bounds, + required this.markerIds, + }); + + final String clusterManagerId; + final PlatformLatLng position; + final PlatformLatLngBounds bounds; + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + final List markerIds; +} + +/// Pigeon equivalent of MapConfiguration. +class PlatformMapConfiguration { + PlatformMapConfiguration({required this.json}); + + /// The configuration options, as JSON. This should only be set from + /// _jsonForMapConfiguration, and the native code must intepret it according + /// to the internal implementation details of that method. + // TODO(stuartmorgan): Replace this with structured data. This exists only to + // allow incremental migration to Pigeon. + final Object json; +} + +/// Pigeon representation of an x,y coordinate. +class PlatformPoint { + PlatformPoint({required this.x, required this.y}); + + final int x; + final int y; +} + +/// Pigeon equivalent of native TileOverlay properties. +class PlatformTileLayer { + PlatformTileLayer({ + required this.visible, + required this.fadeIn, + required this.transparency, + required this.zIndex, + }); + + final bool visible; + final bool fadeIn; + final double transparency; + final double zIndex; +} + +/// Possible outcomes of launching a URL. +class PlatformZoomRange { + PlatformZoomRange({required this.min, required this.max}); + + final double min; + final double max; +} + +/// Interface for non-test interactions with the native SDK. +/// +/// For test-only state queries, see [MapsInspectorApi]. +@HostApi() +abstract class MapsApi { + /// Returns once the map instance is available. + @async + void waitForMap(); + + /// Updates the map's configuration options. + /// + /// Only non-null configuration values will result in updates; options with + /// null values will remain unchanged. + void updateMapConfiguration(PlatformMapConfiguration configuration); + + /// Updates the set of circles on the map. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + void updateCircles(List toAdd, + List toChange, List idsToRemove); + + /// Updates the set of custer managers for clusters on the map. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + void updateClusterManagers( + List toAdd, List idsToRemove); + + /// Updates the set of markers on the map. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + void updateMarkers(List toAdd, + List toChange, List idsToRemove); + + /// Updates the set of polygonss on the map. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + void updatePolygons(List toAdd, + List toChange, List idsToRemove); + + /// Updates the set of polylines on the map. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + void updatePolylines(List toAdd, + List toChange, List idsToRemove); + + /// Updates the set of tile overlays on the map. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + void updateTileOverlays(List toAdd, + List toChange, List idsToRemove); + + /// Gets the screen coordinate for the given map location. + PlatformPoint getScreenCoordinate(PlatformLatLng latLng); + + /// Gets the map location for the given screen coordinate. + PlatformLatLng getLatLng(PlatformPoint screenCoordinate); + + /// Gets the map region currently displayed on the map. + PlatformLatLngBounds getVisibleRegion(); + + /// Moves the camera according to [cameraUpdate] immediately, with no + /// animation. + void moveCamera(PlatformCameraUpdate cameraUpdate); + + /// Moves the camera according to [cameraUpdate], animating the update. + void animateCamera(PlatformCameraUpdate cameraUpdate); + + /// Gets the current map zoom level. + double getZoomLevel(); + + /// Show the info window for the marker with the given ID. + void showInfoWindow(String markerId); + + /// Hide the info window for the marker with the given ID. + void hideInfoWindow(String markerId); + + /// Returns true if the marker with the given ID is currently displaying its + /// info window. + bool isInfoWindowShown(String markerId); + + /// Sets the style to the given map style string, where an empty string + /// indicates that the style should be cleared. + /// + /// Returns false if there was an error setting the style, such as an invalid + /// style string. + bool setStyle(String style); + + /// Returns true if the last attempt to set a style, either via initial map + /// style or setMapStyle, succeeded. + /// + /// This allows checking asynchronously for initial style failures, as there + /// is no way to return failures from map initialization. + bool didLastStyleSucceed(); + + /// Clears the cache of tiles previously requseted from the tile provider. + void clearTileCache(String tileOverlayId); + + /// Takes a snapshot of the map and returns its image data. + @async + Uint8List takeSnapshot(); +} + +/// Interface for global SDK initialization. +@HostApi() +abstract class MapsInitializerApi { + /// Initializes the Google Maps SDK with the given renderer preference. + /// + /// A null renderer preference will result in the default renderer. + /// + /// Calling this more than once in the lifetime of an application will result + /// in an error. + @async + PlatformRendererType initializeWithPreferredRenderer( + PlatformRendererType? type); +} + +/// Inspector API only intended for use in integration tests. +@HostApi() +abstract class MapsInspectorApi { + bool areBuildingsEnabled(); + bool areRotateGesturesEnabled(); + bool areZoomControlsEnabled(); + bool areScrollGesturesEnabled(); + bool areTiltGesturesEnabled(); + bool areZoomGesturesEnabled(); + bool isCompassEnabled(); + bool? isLiteModeEnabled(); + bool isMapToolbarEnabled(); + bool isMyLocationButtonEnabled(); + bool isTrafficEnabled(); + PlatformTileLayer? getTileOverlayInfo(String tileOverlayId); + PlatformZoomRange getZoomRange(); + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The consuming code treats the entries as non-nullable. + List getClusters(String clusterManagerId); +} 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 1f2a3be877c..e55b16a6b94 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,11 +2,11 @@ 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.7.0 +version: 2.11.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: @@ -21,13 +21,16 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 + build_runner: ^2.3.3 flutter_test: sdk: flutter + mockito: 5.4.4 + pigeon: ^20.0.1 plugin_platform_interface: ^2.1.7 topics: diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 946d84d7661..cfe6b976695 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -8,33 +8,23 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; +import 'package:google_maps_flutter_android/src/messages.g.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'google_maps_flutter_android_test.mocks.dart'; + +@GenerateNiceMocks(>[MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late List log; - - setUp(() async { - log = []; - }); - - /// Initializes a map with the given ID and canned responses, logging all - /// calls to [log]. - void configureMockMap( - GoogleMapsFlutterAndroid maps, { - required int mapId, - required Future? Function(MethodCall call) handler, - }) { - final MethodChannel channel = maps.ensureChannelInitialized(mapId); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - channel, - (MethodCall methodCall) { - log.add(methodCall.method); - return handler(methodCall); - }, - ); + (GoogleMapsFlutterAndroid, MockMapsApi) setUpMockMap({required int mapId}) { + final MockMapsApi api = MockMapsApi(); + final GoogleMapsFlutterAndroid maps = + GoogleMapsFlutterAndroid(apiProvider: (_) => api); + maps.ensureApiInitialized(mapId); + return (maps, api); } Future sendPlatformMessage( @@ -51,39 +41,427 @@ void main() { expect(GoogleMapsFlutterPlatform.instance, isA()); }); - // Calls each method that uses invokeMethod with a return type other than - // void to ensure that the casting/nullability handling succeeds. - // - // TODO(stuartmorgan): Remove this once there is real test coverage of - // each method, since that would cover this issue. - test('non-void invokeMethods handle types correctly', () async { - const int mapId = 0; - final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); - configureMockMap(maps, mapId: mapId, - handler: (MethodCall methodCall) async { - switch (methodCall.method) { - case 'map#getLatLng': - return [1.0, 2.0]; - case 'markers#isInfoWindowShown': - return true; - case 'map#getZoomLevel': - return 2.5; - case 'map#takeSnapshot': - return null; - } - }); - - await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId); - await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId); - await maps.getZoomLevel(mapId: mapId); - await maps.takeSnapshot(mapId: mapId); - // Check that all the invokeMethod calls happened. - expect(log, [ - 'map#getLatLng', - 'markers#isInfoWindowShown', - 'map#getZoomLevel', - 'map#takeSnapshot', - ]); + test('init calls waitForMap', () async { + final MockMapsApi api = MockMapsApi(); + final GoogleMapsFlutterAndroid maps = + GoogleMapsFlutterAndroid(apiProvider: (_) => api); + + await maps.init(1); + + verify(api.waitForMap()); + }); + + test('getScreenCoordinate converts and passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Arbitrary values that are all different from each other. + const LatLng latLng = LatLng(10, 20); + const ScreenCoordinate expectedCoord = ScreenCoordinate(x: 30, y: 40); + when(api.getScreenCoordinate(any)).thenAnswer( + (_) async => PlatformPoint(x: expectedCoord.x, y: expectedCoord.y)); + + final ScreenCoordinate coord = + await maps.getScreenCoordinate(latLng, mapId: mapId); + expect(coord, expectedCoord); + final VerificationResult verification = + verify(api.getScreenCoordinate(captureAny)); + final PlatformLatLng passedLatLng = + verification.captured[0] as PlatformLatLng; + expect(passedLatLng.latitude, latLng.latitude); + expect(passedLatLng.longitude, latLng.longitude); + }); + + test('getLatLng converts and passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Arbitrary values that are all different from each other. + const LatLng expectedLatLng = LatLng(10, 20); + const ScreenCoordinate coord = ScreenCoordinate(x: 30, y: 40); + when(api.getLatLng(any)).thenAnswer((_) async => PlatformLatLng( + latitude: expectedLatLng.latitude, + longitude: expectedLatLng.longitude)); + + final LatLng latLng = await maps.getLatLng(coord, mapId: mapId); + expect(latLng, expectedLatLng); + final VerificationResult verification = verify(api.getLatLng(captureAny)); + final PlatformPoint passedCoord = verification.captured[0] as PlatformPoint; + expect(passedCoord.x, coord.x); + expect(passedCoord.y, coord.y); + }); + + test('getVisibleRegion converts and passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Arbitrary values that are all different from each other. + final LatLngBounds expectedBounds = LatLngBounds( + southwest: const LatLng(10, 20), northeast: const LatLng(30, 40)); + when(api.getVisibleRegion()).thenAnswer((_) async => PlatformLatLngBounds( + southwest: PlatformLatLng( + latitude: expectedBounds.southwest.latitude, + longitude: expectedBounds.southwest.longitude), + northeast: PlatformLatLng( + latitude: expectedBounds.northeast.latitude, + longitude: expectedBounds.northeast.longitude))); + + final LatLngBounds bounds = await maps.getVisibleRegion(mapId: mapId); + expect(bounds, expectedBounds); + }); + + test('moveCamera calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + final CameraUpdate update = CameraUpdate.scrollBy(10, 20); + await maps.moveCamera(update, mapId: mapId); + + final VerificationResult verification = verify(api.moveCamera(captureAny)); + final PlatformCameraUpdate passedUpdate = + verification.captured[0] as PlatformCameraUpdate; + expect(passedUpdate.json, update.toJson()); + }); + + test('animateCamera calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + final CameraUpdate update = CameraUpdate.scrollBy(10, 20); + await maps.animateCamera(update, mapId: mapId); + + final VerificationResult verification = + verify(api.animateCamera(captureAny)); + final PlatformCameraUpdate passedUpdate = + verification.captured[0] as PlatformCameraUpdate; + expect(passedUpdate.json, update.toJson()); + }); + + test('getZoomLevel passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const double expectedZoom = 4.2; + when(api.getZoomLevel()).thenAnswer((_) async => expectedZoom); + + final double zoom = await maps.getZoomLevel(mapId: mapId); + expect(zoom, expectedZoom); + }); + + test('showInfoWindow calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String markedId = 'a_marker'; + await maps.showMarkerInfoWindow(const MarkerId(markedId), mapId: mapId); + + verify(api.showInfoWindow(markedId)); + }); + + test('hideInfoWindow calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String markedId = 'a_marker'; + await maps.hideMarkerInfoWindow(const MarkerId(markedId), mapId: mapId); + + verify(api.hideInfoWindow(markedId)); + }); + + test('isInfoWindowShown calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String markedId = 'a_marker'; + when(api.isInfoWindowShown(markedId)).thenAnswer((_) async => true); + + expect( + await maps.isMarkerInfoWindowShown(const MarkerId(markedId), + mapId: mapId), + true); + }); + + test('takeSnapshot calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + final Uint8List fakeSnapshot = Uint8List(10); + when(api.takeSnapshot()).thenAnswer((_) async => fakeSnapshot); + + expect(await maps.takeSnapshot(mapId: mapId), fakeSnapshot); + }); + + test('clearTileCache calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String tileOverlayId = 'overlay'; + await maps.clearTileCache(const TileOverlayId(tileOverlayId), mapId: mapId); + + verify(api.clearTileCache(tileOverlayId)); + }); + + test('updateMapConfiguration passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Set some arbitrary options. + const MapConfiguration config = MapConfiguration( + compassEnabled: true, + liteModeEnabled: false, + mapType: MapType.terrain, + ); + await maps.updateMapConfiguration(config, mapId: mapId); + + final VerificationResult verification = + verify(api.updateMapConfiguration(captureAny)); + final PlatformMapConfiguration passedConfig = + verification.captured[0] as PlatformMapConfiguration; + final Map passedConfigJson = + passedConfig.json as Map; + // Each set option should be present. + expect(passedConfigJson['compassEnabled'], true); + expect(passedConfigJson['liteModeEnabled'], false); + expect(passedConfigJson['mapType'], MapType.terrain.index); + // Spot-check that unset options are not be present. + expect(passedConfigJson['myLocationEnabled'], isNull); + expect(passedConfigJson['cameraTargetBounds'], isNull); + expect(passedConfigJson['padding'], isNull); + }); + + test('updateMapOptions passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Set some arbitrary options. + final Map config = { + 'compassEnabled': true, + 'liteModeEnabled': false, + 'mapType': MapType.terrain.index, + }; + await maps.updateMapOptions(config, mapId: mapId); + + final VerificationResult verification = + verify(api.updateMapConfiguration(captureAny)); + final PlatformMapConfiguration passedConfig = + verification.captured[0] as PlatformMapConfiguration; + final Map passedConfigJson = + passedConfig.json as Map; + // Each set option should be present. + expect(passedConfigJson['compassEnabled'], true); + expect(passedConfigJson['liteModeEnabled'], false); + expect(passedConfigJson['mapType'], MapType.terrain.index); + // Spot-check that unset options are not be present. + expect(passedConfigJson['myLocationEnabled'], isNull); + expect(passedConfigJson['cameraTargetBounds'], isNull); + expect(passedConfigJson['padding'], isNull); + }); + + test('updateCircles passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const Circle object1 = Circle(circleId: CircleId('1')); + const Circle object2old = Circle(circleId: CircleId('2')); + final Circle object2new = object2old.copyWith(radiusParam: 42); + const Circle object3 = Circle(circleId: CircleId('3')); + await maps.updateCircles( + CircleUpdates.from( + {object1, object2old}, {object2new, object3}), + mapId: mapId); + + final VerificationResult verification = + verify(api.updateCircles(captureAny, captureAny, captureAny)); + final List toAdd = + verification.captured[0] as List; + final List toChange = + verification.captured[1] as List; + final List toRemove = verification.captured[2] as List; + // Object one should be removed. + expect(toRemove.length, 1); + expect(toRemove.first, object1.circleId.value); + // Object two should be changed. + expect(toChange.length, 1); + expect(toChange.first?.json, object2new.toJson()); + // Object 3 should be added. + expect(toAdd.length, 1); + expect(toAdd.first?.json, object3.toJson()); + }); + + test('updateClusterManagers passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const ClusterManager object1 = + ClusterManager(clusterManagerId: ClusterManagerId('1')); + const ClusterManager object3 = + ClusterManager(clusterManagerId: ClusterManagerId('3')); + await maps.updateClusterManagers( + ClusterManagerUpdates.from( + {object1}, {object3}), + mapId: mapId); + + final VerificationResult verification = + verify(api.updateClusterManagers(captureAny, captureAny)); + final List toAdd = + verification.captured[0] as List; + final List toRemove = verification.captured[1] as List; + // Object one should be removed. + expect(toRemove.length, 1); + expect(toRemove.first, object1.clusterManagerId.value); + // Unlike other map object types, changes are not possible for cluster + // managers, since they have no non-ID properties. + // Object 3 should be added. + expect(toAdd.length, 1); + expect(toAdd.first?.identifier, object3.clusterManagerId.value); + }); + + test('updateMarkers passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const Marker object1 = Marker(markerId: MarkerId('1')); + const Marker object2old = Marker(markerId: MarkerId('2')); + final Marker object2new = object2old.copyWith(rotationParam: 42); + const Marker object3 = Marker(markerId: MarkerId('3')); + await maps.updateMarkers( + MarkerUpdates.from( + {object1, object2old}, {object2new, object3}), + mapId: mapId); + + final VerificationResult verification = + verify(api.updateMarkers(captureAny, captureAny, captureAny)); + final List toAdd = + verification.captured[0] as List; + final List toChange = + verification.captured[1] as List; + final List toRemove = verification.captured[2] as List; + // Object one should be removed. + expect(toRemove.length, 1); + expect(toRemove.first, object1.markerId.value); + // Object two should be changed. + expect(toChange.length, 1); + expect(toChange.first?.json, object2new.toJson()); + // Object 3 should be added. + expect(toAdd.length, 1); + expect(toAdd.first?.json, object3.toJson()); + }); + + test('updatePolygons passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const Polygon object1 = Polygon(polygonId: PolygonId('1')); + const Polygon object2old = Polygon(polygonId: PolygonId('2')); + final Polygon object2new = object2old.copyWith(strokeWidthParam: 42); + const Polygon object3 = Polygon(polygonId: PolygonId('3')); + await maps.updatePolygons( + PolygonUpdates.from( + {object1, object2old}, {object2new, object3}), + mapId: mapId); + + final VerificationResult verification = + verify(api.updatePolygons(captureAny, captureAny, captureAny)); + final List toAdd = + verification.captured[0] as List; + final List toChange = + verification.captured[1] as List; + final List toRemove = verification.captured[2] as List; + // Object one should be removed. + expect(toRemove.length, 1); + expect(toRemove.first, object1.polygonId.value); + // Object two should be changed. + expect(toChange.length, 1); + expect(toChange.first?.json, object2new.toJson()); + // Object 3 should be added. + expect(toAdd.length, 1); + expect(toAdd.first?.json, object3.toJson()); + }); + + test('updatePolylines passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const Polyline object1 = Polyline(polylineId: PolylineId('1')); + const Polyline object2old = Polyline(polylineId: PolylineId('2')); + final Polyline object2new = object2old.copyWith(widthParam: 42); + const Polyline object3 = Polyline(polylineId: PolylineId('3')); + await maps.updatePolylines( + PolylineUpdates.from( + {object1, object2old}, {object2new, object3}), + mapId: mapId); + + final VerificationResult verification = + verify(api.updatePolylines(captureAny, captureAny, captureAny)); + final List toAdd = + verification.captured[0] as List; + final List toChange = + verification.captured[1] as List; + final List toRemove = verification.captured[2] as List; + // Object one should be removed. + expect(toRemove.length, 1); + expect(toRemove.first, object1.polylineId.value); + // Object two should be changed. + expect(toChange.length, 1); + expect(toChange.first?.json, object2new.toJson()); + // Object 3 should be added. + expect(toAdd.length, 1); + expect(toAdd.first?.json, object3.toJson()); + }); + + test('updateTileOverlays passes expected arguments', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const TileOverlay object1 = TileOverlay(tileOverlayId: TileOverlayId('1')); + const TileOverlay object2old = + TileOverlay(tileOverlayId: TileOverlayId('2')); + final TileOverlay object2new = object2old.copyWith(zIndexParam: 42); + const TileOverlay object3 = TileOverlay(tileOverlayId: TileOverlayId('3')); + // Pre-set the initial state, since this update method doesn't take the old + // state. + await maps.updateTileOverlays( + newTileOverlays: {object1, object2old}, mapId: mapId); + clearInteractions(api); + + await maps.updateTileOverlays( + newTileOverlays: {object2new, object3}, mapId: mapId); + + final VerificationResult verification = + verify(api.updateTileOverlays(captureAny, captureAny, captureAny)); + final List toAdd = + verification.captured[0] as List; + final List toChange = + verification.captured[1] as List; + final List toRemove = verification.captured[2] as List; + // Object one should be removed. + expect(toRemove.length, 1); + expect(toRemove.first, object1.tileOverlayId.value); + // Object two should be changed. + expect(toChange.length, 1); + expect(toChange.first?.json, object2new.toJson()); + // Object 3 should be added. + expect(toAdd.length, 1); + expect(toAdd.first?.json, object3.toJson()); }); test('markers send drag event to correct streams', () async { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart new file mode 100644 index 00000000000..c49d6268926 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart @@ -0,0 +1,372 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in google_maps_flutter_android/test/google_maps_flutter_android_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:typed_data' as _i4; + +import 'package:google_maps_flutter_android/src/messages.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformPoint_0 extends _i1.SmartFake implements _i2.PlatformPoint { + _FakePlatformPoint_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformLatLng_1 extends _i1.SmartFake + implements _i2.PlatformLatLng { + _FakePlatformLatLng_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformLatLngBounds_2 extends _i1.SmartFake + implements _i2.PlatformLatLngBounds { + _FakePlatformLatLngBounds_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [MapsApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMapsApi extends _i1.Mock implements _i2.MapsApi { + @override + _i3.Future waitForMap() => (super.noSuchMethod( + Invocation.method( + #waitForMap, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updateMapConfiguration( + _i2.PlatformMapConfiguration? configuration) => + (super.noSuchMethod( + Invocation.method( + #updateMapConfiguration, + [configuration], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updateCircles( + List<_i2.PlatformCircle?>? toAdd, + List<_i2.PlatformCircle?>? toChange, + List? idsToRemove, + ) => + (super.noSuchMethod( + Invocation.method( + #updateCircles, + [ + toAdd, + toChange, + idsToRemove, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updateClusterManagers( + List<_i2.PlatformClusterManager?>? toAdd, + List? idsToRemove, + ) => + (super.noSuchMethod( + Invocation.method( + #updateClusterManagers, + [ + toAdd, + idsToRemove, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updateMarkers( + List<_i2.PlatformMarker?>? toAdd, + List<_i2.PlatformMarker?>? toChange, + List? idsToRemove, + ) => + (super.noSuchMethod( + Invocation.method( + #updateMarkers, + [ + toAdd, + toChange, + idsToRemove, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updatePolygons( + List<_i2.PlatformPolygon?>? toAdd, + List<_i2.PlatformPolygon?>? toChange, + List? idsToRemove, + ) => + (super.noSuchMethod( + Invocation.method( + #updatePolygons, + [ + toAdd, + toChange, + idsToRemove, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updatePolylines( + List<_i2.PlatformPolyline?>? toAdd, + List<_i2.PlatformPolyline?>? toChange, + List? idsToRemove, + ) => + (super.noSuchMethod( + Invocation.method( + #updatePolylines, + [ + toAdd, + toChange, + idsToRemove, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future updateTileOverlays( + List<_i2.PlatformTileOverlay?>? toAdd, + List<_i2.PlatformTileOverlay?>? toChange, + List? idsToRemove, + ) => + (super.noSuchMethod( + Invocation.method( + #updateTileOverlays, + [ + toAdd, + toChange, + idsToRemove, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future<_i2.PlatformPoint> getScreenCoordinate( + _i2.PlatformLatLng? latLng) => + (super.noSuchMethod( + Invocation.method( + #getScreenCoordinate, + [latLng], + ), + returnValue: _i3.Future<_i2.PlatformPoint>.value(_FakePlatformPoint_0( + this, + Invocation.method( + #getScreenCoordinate, + [latLng], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.PlatformPoint>.value(_FakePlatformPoint_0( + this, + Invocation.method( + #getScreenCoordinate, + [latLng], + ), + )), + ) as _i3.Future<_i2.PlatformPoint>); + + @override + _i3.Future<_i2.PlatformLatLng> getLatLng( + _i2.PlatformPoint? screenCoordinate) => + (super.noSuchMethod( + Invocation.method( + #getLatLng, + [screenCoordinate], + ), + returnValue: _i3.Future<_i2.PlatformLatLng>.value(_FakePlatformLatLng_1( + this, + Invocation.method( + #getLatLng, + [screenCoordinate], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.PlatformLatLng>.value(_FakePlatformLatLng_1( + this, + Invocation.method( + #getLatLng, + [screenCoordinate], + ), + )), + ) as _i3.Future<_i2.PlatformLatLng>); + + @override + _i3.Future<_i2.PlatformLatLngBounds> getVisibleRegion() => + (super.noSuchMethod( + Invocation.method( + #getVisibleRegion, + [], + ), + returnValue: _i3.Future<_i2.PlatformLatLngBounds>.value( + _FakePlatformLatLngBounds_2( + this, + Invocation.method( + #getVisibleRegion, + [], + ), + )), + returnValueForMissingStub: _i3.Future<_i2.PlatformLatLngBounds>.value( + _FakePlatformLatLngBounds_2( + this, + Invocation.method( + #getVisibleRegion, + [], + ), + )), + ) as _i3.Future<_i2.PlatformLatLngBounds>); + + @override + _i3.Future moveCamera(_i2.PlatformCameraUpdate? cameraUpdate) => + (super.noSuchMethod( + Invocation.method( + #moveCamera, + [cameraUpdate], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future animateCamera(_i2.PlatformCameraUpdate? cameraUpdate) => + (super.noSuchMethod( + Invocation.method( + #animateCamera, + [cameraUpdate], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future getZoomLevel() => (super.noSuchMethod( + Invocation.method( + #getZoomLevel, + [], + ), + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); + + @override + _i3.Future showInfoWindow(String? markerId) => (super.noSuchMethod( + Invocation.method( + #showInfoWindow, + [markerId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future hideInfoWindow(String? markerId) => (super.noSuchMethod( + Invocation.method( + #hideInfoWindow, + [markerId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future isInfoWindowShown(String? markerId) => (super.noSuchMethod( + Invocation.method( + #isInfoWindowShown, + [markerId], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future setStyle(String? style) => (super.noSuchMethod( + Invocation.method( + #setStyle, + [style], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future didLastStyleSucceed() => (super.noSuchMethod( + Invocation.method( + #didLastStyleSucceed, + [], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future clearTileCache(String? tileOverlayId) => (super.noSuchMethod( + Invocation.method( + #clearTileCache, + [tileOverlayId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future<_i4.Uint8List> takeSnapshot() => (super.noSuchMethod( + Invocation.method( + #takeSnapshot, + [], + ), + returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)), + returnValueForMissingStub: + _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)), + ) as _i3.Future<_i4.Uint8List>); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS index 9f1b53ee266..4fc3ace39f0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS +++ b/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS @@ -65,3 +65,4 @@ Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> Taha Tesser +Joonas Kerttula diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md index cf9231d6a0a..4802eb9d144 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.8.1 + +* Improves Objective-C type handling. + +## 2.8.0 + +* Adds compatibility with SDK version 9.x for apps targetting iOS 15+. + +## 2.7.0 + +* Adds support for BitmapDescriptor classes `AssetMapBitmap` and `BytesMapBitmap`. + +## 2.6.1 + +* Adds support for patterns in polylines. + ## 2.6.0 * Updates the minimum allowed verison of the Google Maps SDK to 8.4, for privacy diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/README.md b/packages/google_maps_flutter/google_maps_flutter_ios/README.md index 5594c6f2bfe..6b481626dbc 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/README.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/google_maps_flutter -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/google_maps_test.dart index e49c451d08e..db8b3956763 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/google_maps_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/google_maps_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:typed_data'; import 'dart:ui' as ui; @@ -12,6 +13,8 @@ import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf import 'package:integration_test/integration_test.dart'; import 'package:maps_example_dart/example_google_map.dart'; +import 'resources/icon_image_base64.dart'; + const LatLng _kInitialMapCenter = LatLng(0, 0); const double _kInitialZoomLevel = 5; const CameraPosition _kInitialCameraPosition = @@ -818,19 +821,6 @@ void main() { expect(iwVisibleStatus, false); }); - testWidgets('fromAssetImage', (WidgetTester tester) async { - const double pixelRatio = 2; - const ImageConfiguration imageConfiguration = - ImageConfiguration(devicePixelRatio: pixelRatio); - final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage( - imageConfiguration, 'red_square.png'); - final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage( - imageConfiguration, 'red_square.png', - mipmaps: false); - expect((mip.toJson() as List)[2], 1); - expect((scaled.toJson() as List)[2], 2); - }); - testWidgets('testTakeSnapshot', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); @@ -1100,6 +1090,125 @@ void main() { final String? error = await controller.getStyleError(); expect(error, isNull); }); + + testWidgets('markerWithAssetMapBitmap', (WidgetTester tester) async { + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/red_square.png', + imagePixelRatio: 1.0, + )), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + + await tester.pumpAndSettle(); + }); + + testWidgets('markerWithAssetMapBitmapCreate', (WidgetTester tester) async { + final ImageConfiguration imageConfiguration = ImageConfiguration( + devicePixelRatio: tester.view.devicePixelRatio, + ); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: await AssetMapBitmap.create( + imageConfiguration, + 'assets/red_square.png', + )), + }; + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + + await tester.pumpAndSettle(); + }); + + testWidgets('markerWithBytesMapBitmap', (WidgetTester tester) async { + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: BytesMapBitmap( + bytes, + imagePixelRatio: tester.view.devicePixelRatio, + ), + ), + }; + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + ), + )); + + await tester.pumpAndSettle(); + }); + + testWidgets('markerWithLegacyAsset', (WidgetTester tester) async { + //tester.view.devicePixelRatio = 2.0; + const ImageConfiguration imageConfiguration = ImageConfiguration( + devicePixelRatio: 2.0, + size: Size(100, 100), + ); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: await BitmapDescriptor.fromAssetImage( + imageConfiguration, + 'assets/red_square.png', + )), + }; + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + onMapCreated: (ExampleGoogleMapController controller) => + controllerCompleter.complete(controller), + ), + )); + + await controllerCompleter.future; + }); + + testWidgets('markerWithLegacyBytes', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final BitmapDescriptor icon = BitmapDescriptor.fromBytes( + bytes, + ); + + final Set markers = { + Marker(markerId: const MarkerId('1'), icon: icon), + }; + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + markers: markers, + onMapCreated: (ExampleGoogleMapController controller) => + controllerCompleter.complete(controller), + ), + )); + await controllerCompleter.future; + }); } class _DebugTileProvider implements TileProvider { diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/resources/icon_image.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/resources/icon_image.png new file mode 100644 index 00000000000..920b93f74d7 Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/resources/icon_image.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/resources/icon_image_base64.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/resources/icon_image_base64.dart new file mode 100644 index 00000000000..1bfc791ca38 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/integration_test/resources/icon_image_base64.dart @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// This constant holds the base64-encoded data of a 16x16 PNG image of the +/// Flutter logo. +/// +/// See `icon_image.png` source in the same directory. +/// +/// To create or update this image, follow these steps: +/// 1. Create or update a 16x16 PNG image. +/// 2. Convert the image to a base64 string using a script below. +/// 3. Replace the existing base64 string below with the new one. +/// +/// Example of converting an image to base64 in Dart: +/// ```dart +/// import 'dart:convert'; +/// import 'dart:io'; +/// +/// void main() async { +/// final bytes = await File('icon_image.png').readAsBytes(); +/// final base64String = base64Encode(bytes); +/// print(base64String); +/// } +/// ``` +const String iconImageBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAIRlWElmTU' + '0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIA' + 'AIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQ' + 'AAABCgAwAEAAAAAQAAABAAAAAAx28c8QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1M' + 'OmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIH' + 'g6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8v' + 'd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcm' + 'lwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFk' + 'b2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk' + '9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6' + 'eG1wbWV0YT4KTMInWQAAAplJREFUOBF1k01ME1EQx2fe7tIPoGgTE6AJgQQSPaiH9oAtkFbsgX' + 'jygFcT0XjSkxcTDxtPJh6MR28ePMHBBA8cNLSIony0oBhEMVETP058tE132+7uG3cW24DAXN57' + '2fn9/zPz3iIcEdEl0nIxtNLr1IlVeoMadkubKmoL+u2SzAV8IjV5Ekt4GN+A8+VOUPwLarOI2G' + 'Vpqq0i4JQorwQxPtWHVZ1IKP8LNGDXGaSyqARFxDGo7MJBy4XVf3AyQ+qTHnTEXoF9cFUy3OkY' + '0oWxmWFtD5xNoc1sQ6AOn1+hCNTkkhKow8KFZV77tVs2O9dhFvBm0IA/U0RhZ7/ocEx23oUDlh' + 'h8HkNjZIN8Lb3gOU8gOp7AKJHCB2/aNZkTftHumNzzbtl2CBPZHqxw8mHhVZBeoz6w5DvhE2FZ' + 'lQYPjKdd2/qRyKZ6KsPv7TEk7EYEk0A0EUmJduHRy1i4oLKqgmC59ZggAdwrC9pFuWy1iUT2rA' + 'uv0h2UdNtNqxCBBkgqorjOMOgksN7CxQ90vEb00U3c3LIwyo9o8FXxQVNr8Coqyk+S5EPBXnjt' + 'xRmc4TegI7qWbvBkeeUbGMnTCd4nZnYeDOWIEtlC6cKK/JJepY3hZSvN33jovO6L0XFqPKqBTO' + 'FuapUoPr1lxDM7cmC2TAOz25cYSGa++feBew/cjpc0V+mNT29/HZp3KDFTNLvuTRPEHy5065lj' + 'Xn4y41XM+wP/AlcycRmdc3MUhvLm/J/ceu/3qUVT62oP2EZpjSylHybHSpDUVcjq9gEBVo0+Xt' + 'JyN2IWRO+3QUforRoKnZLVsglaMECW+YmMSj9M3SrC6Lg71CMiqWfUrJ6ywzefhnZ+G69BaKdB' + 'WhXQAn6wzDUpfUPw7MrmX/WhbfmKblw+AAAAAElFTkSuQmCC'; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj index d5b6d8248e0..10545d750ed 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/Runner.xcodeproj/project.pbxproj @@ -11,15 +11,17 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; + 478116522BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 478116512BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m */; }; 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */; }; + 521AB0032B876A76005F460D /* ExtractIconFromDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 521AB0022B876A76005F460D /* ExtractIconFromDataTests.m */; }; 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68E472692836FF0C00BDDDAC /* MapKit.framework */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - F269303B2BB389BF00BF17C4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = F269303A2BB389BF00BF17C4 /* assets */; }; 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; }; + F269303B2BB389BF00BF17C4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = F269303A2BB389BF00BF17C4 /* assets */; }; F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; }; F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */; }; FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; }; @@ -60,7 +62,9 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 478116512BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsPolylinesControllerTests.m; sourceTree = ""; }; 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTGoogleMapJSONConversionsConversionTests.m; sourceTree = ""; }; + 521AB0022B876A76005F460D /* ExtractIconFromDataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExtractIconFromDataTests.m; sourceTree = ""; }; 68E472692836FF0C00BDDDAC /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -202,7 +206,9 @@ children = ( F269303A2BB389BF00BF17C4 /* assets */, 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */, + 521AB0022B876A76005F460D /* ExtractIconFromDataTests.m */, F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */, + 478116512BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m */, 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */, 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */, F7151F14265D7ED70028CB91 /* Info.plist */, @@ -467,7 +473,9 @@ F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */, 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */, 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */, + 478116522BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m in Sources */, 0DD7B6C32B744EEF00E857FD /* FLTTileProviderControllerTests.m in Sources */, + 521AB0032B876A76005F460D /* ExtractIconFromDataTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/ExtractIconFromDataTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/ExtractIconFromDataTests.m new file mode 100644 index 00000000000..c3d6a363e0c --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/ExtractIconFromDataTests.m @@ -0,0 +1,371 @@ +// 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 google_maps_flutter_ios; +@import google_maps_flutter_ios.Test; +@import XCTest; +#import +#import + +@interface ExtractIconFromDataTests : XCTestCase +- (UIImage *)createOnePixelImage; +@end + +@implementation ExtractIconFromDataTests + +- (void)testExtractIconFromDataAssetAuto { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + id mockImageClass = OCMClassMock([UIImage class]); + UIImage *testImage = [self createOnePixelImage]; + OCMStub([mockRegistrar lookupKeyForAsset:@"fakeImageNameKey"]).andReturn(@"fakeAssetKey"); + OCMStub(ClassMethod([mockImageClass imageNamed:@"fakeAssetKey"])).andReturn(testImage); + + NSDictionary *assetData = + @{@"assetName" : @"fakeImageNameKey", @"bitmapScaling" : @"auto", @"imagePixelRatio" : @1}; + + NSArray *iconData = @[ @"asset", assetData ]; + + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, 1.0); + XCTAssertEqual(resultImage.size.width, 1.0); + XCTAssertEqual(resultImage.size.height, 1.0); +} + +- (void)testExtractIconFromDataAssetAutoWithScale { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + id mockImageClass = OCMClassMock([UIImage class]); + UIImage *testImage = [self createOnePixelImage]; + + OCMStub([mockRegistrar lookupKeyForAsset:@"fakeImageNameKey"]).andReturn(@"fakeAssetKey"); + OCMStub(ClassMethod([mockImageClass imageNamed:@"fakeAssetKey"])).andReturn(testImage); + + NSDictionary *assetData = + @{@"assetName" : @"fakeImageNameKey", @"bitmapScaling" : @"auto", @"imagePixelRatio" : @10}; + + NSArray *iconData = @[ @"asset", assetData ]; + + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, 10); + XCTAssertEqual(resultImage.size.width, 0.1); + XCTAssertEqual(resultImage.size.height, 0.1); +} + +- (void)testExtractIconFromDataAssetAutoAndSizeWithSameAspectRatio { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + id mockImageClass = OCMClassMock([UIImage class]); + UIImage *testImage = [self createOnePixelImage]; + XCTAssertEqual(testImage.scale, 1.0); + + OCMStub([mockRegistrar lookupKeyForAsset:@"fakeImageNameKey"]).andReturn(@"fakeAssetKey"); + OCMStub(ClassMethod([mockImageClass imageNamed:@"fakeAssetKey"])).andReturn(testImage); + + NSDictionary *assetData = @{ + @"assetName" : @"fakeImageNameKey", + @"bitmapScaling" : @"auto", + @"imagePixelRatio" : @1, + @"width" : @15.0 + }; // Target height + + NSArray *iconData = @[ @"asset", assetData ]; + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + XCTAssertNotNil(resultImage); + XCTAssertEqual(testImage.scale, 1.0); + + // As image has same aspect ratio as the original image, + // only image scale has been changed to match the target size. + CGFloat targetScale = testImage.scale * (testImage.size.width / 15.0); + const CGFloat accuracy = 0.001; + XCTAssertEqualWithAccuracy(resultImage.scale, targetScale, accuracy); + XCTAssertEqual(resultImage.size.width, 15.0); + XCTAssertEqual(resultImage.size.height, 15.0); +} + +- (void)testExtractIconFromDataAssetAutoAndSizeWithDifferentAspectRatio { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + id mockImageClass = OCMClassMock([UIImage class]); + UIImage *testImage = [self createOnePixelImage]; + + OCMStub([mockRegistrar lookupKeyForAsset:@"fakeImageNameKey"]).andReturn(@"fakeAssetKey"); + OCMStub(ClassMethod([mockImageClass imageNamed:@"fakeAssetKey"])).andReturn(testImage); + + NSDictionary *assetData = @{ + @"assetName" : @"fakeImageNameKey", + @"bitmapScaling" : @"auto", + @"imagePixelRatio" : @1, + @"width" : @15.0, + @"height" : @45.0 + }; + + NSArray *iconData = @[ @"asset", assetData ]; + + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, screenScale); + XCTAssertEqual(resultImage.size.width, 15.0); + XCTAssertEqual(resultImage.size.height, 45.0); +} + +- (void)testExtractIconFromDataAssetNoScaling { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + id mockImageClass = OCMClassMock([UIImage class]); + UIImage *testImage = [self createOnePixelImage]; + + OCMStub([mockRegistrar lookupKeyForAsset:@"fakeImageNameKey"]).andReturn(@"fakeAssetKey"); + OCMStub(ClassMethod([mockImageClass imageNamed:@"fakeAssetKey"])).andReturn(testImage); + + NSDictionary *assetData = + @{@"assetName" : @"fakeImageNameKey", @"bitmapScaling" : @"none", @"imagePixelRatio" : @10}; + + NSArray *iconData = @[ @"asset", assetData ]; + + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, 1.0); + XCTAssertEqual(resultImage.size.width, 1.0); + XCTAssertEqual(resultImage.size.height, 1.0); +} + +- (void)testExtractIconFromDataBytesAuto { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + UIImage *testImage = [self createOnePixelImage]; + NSData *pngData = UIImagePNGRepresentation(testImage); + XCTAssertNotNil(pngData); + + FlutterStandardTypedData *typedData = [FlutterStandardTypedData typedDataWithBytes:pngData]; + + NSDictionary *bytesData = + @{@"byteData" : typedData, @"bitmapScaling" : @"auto", @"imagePixelRatio" : @1}; + + NSArray *iconData = @[ @"bytes", bytesData ]; + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, 1.0); + XCTAssertEqual(resultImage.size.width, 1.0); + XCTAssertEqual(resultImage.size.height, 1.0); +} + +- (void)testExtractIconFromDataBytesAutoWithScaling { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + UIImage *testImage = [self createOnePixelImage]; + NSData *pngData = UIImagePNGRepresentation(testImage); + XCTAssertNotNil(pngData); + + FlutterStandardTypedData *typedData = [FlutterStandardTypedData typedDataWithBytes:pngData]; + + NSDictionary *bytesData = + @{@"byteData" : typedData, @"bitmapScaling" : @"auto", @"imagePixelRatio" : @10}; + + NSArray *iconData = @[ @"bytes", bytesData ]; + + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, 10); + XCTAssertEqual(resultImage.size.width, 0.1); + XCTAssertEqual(resultImage.size.height, 0.1); +} + +- (void)testExtractIconFromDataBytesAutoAndSizeWithSameAspectRatio { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + UIImage *testImage = [self createOnePixelImage]; + NSData *pngData = UIImagePNGRepresentation(testImage); + XCTAssertNotNil(pngData); + + FlutterStandardTypedData *typedData = [FlutterStandardTypedData typedDataWithBytes:pngData]; + + NSDictionary *bytesData = @{ + @"byteData" : typedData, + @"bitmapScaling" : @"auto", + @"imagePixelRatio" : @1, + @"width" : @15.0, + @"height" : @15.0 + }; + + NSArray *iconData = @[ @"bytes", bytesData ]; + + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + + XCTAssertNotNil(resultImage); + XCTAssertEqual(testImage.scale, 1.0); + + // As image has same aspect ratio as the original image, + // only image scale has been changed to match the target size. + CGFloat targetScale = testImage.scale * (testImage.size.width / 15.0); + const CGFloat accuracy = 0.001; + XCTAssertEqualWithAccuracy(resultImage.scale, targetScale, accuracy); + XCTAssertEqual(resultImage.size.width, 15.0); + XCTAssertEqual(resultImage.size.height, 15.0); +} + +- (void)testExtractIconFromDataBytesAutoAndSizeWithDifferentAspectRatio { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + UIImage *testImage = [self createOnePixelImage]; + NSData *pngData = UIImagePNGRepresentation(testImage); + XCTAssertNotNil(pngData); + + FlutterStandardTypedData *typedData = [FlutterStandardTypedData typedDataWithBytes:pngData]; + + NSDictionary *bytesData = @{ + @"byteData" : typedData, + @"bitmapScaling" : @"auto", + @"imagePixelRatio" : @1, + @"width" : @15.0, + @"height" : @45.0 + }; + + NSArray *iconData = @[ @"bytes", bytesData ]; + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, screenScale); + XCTAssertEqual(resultImage.size.width, 15.0); + XCTAssertEqual(resultImage.size.height, 45.0); +} + +- (void)testExtractIconFromDataBytesNoScaling { + FLTGoogleMapMarkerController *instance = [[FLTGoogleMapMarkerController alloc] init]; + NSObject *mockRegistrar = + OCMStrictProtocolMock(@protocol(FlutterPluginRegistrar)); + UIImage *testImage = [self createOnePixelImage]; + NSData *pngData = UIImagePNGRepresentation(testImage); + XCTAssertNotNil(pngData); + + FlutterStandardTypedData *typedData = [FlutterStandardTypedData typedDataWithBytes:pngData]; + + NSDictionary *bytesData = + @{@"byteData" : typedData, @"bitmapScaling" : @"none", @"imagePixelRatio" : @1}; + + NSArray *iconData = @[ @"bytes", bytesData ]; + CGFloat screenScale = 3.0; + + UIImage *resultImage = [instance extractIconFromData:iconData + registrar:mockRegistrar + screenScale:screenScale]; + XCTAssertNotNil(resultImage); + XCTAssertEqual(resultImage.scale, 1.0); + XCTAssertEqual(resultImage.size.width, 1.0); + XCTAssertEqual(resultImage.size.height, 1.0); +} + +- (void)testIsScalableWithScaleFactorFromSize100x100to10x100 { + CGSize originalSize = CGSizeMake(100.0, 100.0); + CGSize targetSize = CGSizeMake(10.0, 100.0); + XCTAssertFalse([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (void)testIsScalableWithScaleFactorFromSize100x100to10x10 { + CGSize originalSize = CGSizeMake(100.0, 100.0); + CGSize targetSize = CGSizeMake(10.0, 10.0); + XCTAssertTrue([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (void)testIsScalableWithScaleFactorFromSize233x200to23x20 { + CGSize originalSize = CGSizeMake(233.0, 200.0); + CGSize targetSize = CGSizeMake(23.0, 20.0); + XCTAssertTrue([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (void)testIsScalableWithScaleFactorFromSize233x200to22x20 { + CGSize originalSize = CGSizeMake(233.0, 200.0); + CGSize targetSize = CGSizeMake(22.0, 20.0); + XCTAssertFalse([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (void)testIsScalableWithScaleFactorFromSize200x233to20x23 { + CGSize originalSize = CGSizeMake(200.0, 233.0); + CGSize targetSize = CGSizeMake(20.0, 23.0); + XCTAssertTrue([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (void)testIsScalableWithScaleFactorFromSize200x233to20x22 { + CGSize originalSize = CGSizeMake(200.0, 233.0); + CGSize targetSize = CGSizeMake(20.0, 22.0); + XCTAssertFalse([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (void)testIsScalableWithScaleFactorFromSize1024x768to500x250 { + CGSize originalSize = CGSizeMake(1024.0, 768.0); + CGSize targetSize = CGSizeMake(500.0, 250.0); + XCTAssertFalse([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalSize + toSize:targetSize]); +} + +- (UIImage *)createOnePixelImage { + CGSize size = CGSizeMake(1, 1); + UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat]; + format.scale = 1.0; + format.opaque = YES; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size + format:format]; + UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) { + [[UIColor whiteColor] setFill]; + [context fillRect:CGRectMake(0, 0, size.width, size.height)]; + }]; + return image; +} + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m index 0d7d3ee9b2b..e75fc046912 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m @@ -16,6 +16,28 @@ @interface FLTGoogleMapJSONConversionsTests : XCTestCase @implementation FLTGoogleMapJSONConversionsTests +- (void)testGetValueOrNilWithValue { + NSString *key = @"key"; + NSString *value = @"value"; + NSDictionary *dict = @{key : value}; + + XCTAssertEqual(FGMGetValueOrNilFromDict(dict, key), value); +} + +- (void)testGetValueOrNilWithNoEntry { + NSString *key = @"key"; + NSDictionary *dict = @{}; + + XCTAssertNil(FGMGetValueOrNilFromDict(dict, key)); +} + +- (void)testGetValueOrNilWithNSNull { + NSString *key = @"key"; + NSDictionary *dict = @{key : [NSNull null]}; + + XCTAssertNil(FGMGetValueOrNilFromDict(dict, key)); +} + - (void)testLocationFromLatLong { NSArray *latlong = @[ @1, @2 ]; CLLocationCoordinate2D location = [FLTGoogleMapJSONConversions locationFromLatLong:latlong]; @@ -288,4 +310,18 @@ - (void)testCameraUpdateFromChannelValueZoomTo { [classMockCameraUpdate stopMocking]; } +- (void)testLengthsFromPatterns { + NSArray *> *patterns = @[ @[ @"gap", @10 ], @[ @"dash", @6.4 ] ]; + + NSArray *spanLengths = [FLTGoogleMapJSONConversions spanLengthsFromPatterns:patterns]; + + XCTAssertEqual([spanLengths count], 2); + + NSNumber *firstSpanLength = spanLengths[0]; + NSNumber *secondSpanLength = spanLengths[1]; + + XCTAssertEqual(firstSpanLength.doubleValue, 10); + XCTAssertEqual(secondSpanLength.doubleValue, 6.4); +} + @end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsPolylinesControllerTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsPolylinesControllerTests.m new file mode 100644 index 00000000000..84714f17c7f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/ios/RunnerTests/GoogleMapsPolylinesControllerTests.m @@ -0,0 +1,81 @@ +// 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 google_maps_flutter_ios; +@import google_maps_flutter_ios.Test; +@import XCTest; +@import GoogleMaps; + +#import +#import +#import "PartiallyMockedMapView.h" + +@interface GoogleMapsPolylinesControllerTests : XCTestCase +@end + +@implementation GoogleMapsPolylinesControllerTests + +/// Returns GoogleMapPolylineController object instantiated with a mocked map instance +/// +/// @return An object of FLTGoogleMapPolylineController +- (FLTGoogleMapPolylineController *)polylineControllerWithMockedMap { + NSDictionary *polyline = @{ + @"points" : @[ + @[ @(52.4816), @(-3.1791) ], @[ @(54.043), @(-2.9925) ], @[ @(54.1396), @(-4.2739) ], + @[ @(53.4153), @(-4.0829) ] + ], + @"polylineId" : @"polyline_id_0", + }; + + CGRect frame = CGRectMake(0, 0, 100, 100); + GMSCameraPosition *camera = [[GMSCameraPosition alloc] initWithLatitude:0 longitude:0 zoom:0]; + + GMSMapViewOptions *mapViewOptions = [[GMSMapViewOptions alloc] init]; + mapViewOptions.frame = frame; + mapViewOptions.camera = camera; + + PartiallyMockedMapView *mapView = [[PartiallyMockedMapView alloc] initWithOptions:mapViewOptions]; + + GMSMutablePath *path = [FLTPolylinesController pathForPolyline:polyline]; + NSString *identifier = polyline[@"polylineId"]; + + FLTGoogleMapPolylineController *polylineControllerWithMockedMap = + [[FLTGoogleMapPolylineController alloc] initPolylineWithPath:path + identifier:identifier + mapView:mapView]; + + return polylineControllerWithMockedMap; +} + +- (void)testSetPatterns { + NSArray *styles = @[ + [GMSStrokeStyle solidColor:[UIColor clearColor]], [GMSStrokeStyle solidColor:[UIColor redColor]] + ]; + + NSArray *lengths = @[ @10, @10 ]; + + FLTGoogleMapPolylineController *polylineController = [self polylineControllerWithMockedMap]; + + XCTAssertNil(polylineController.polyline.spans); + + [polylineController setPattern:styles lengths:lengths]; + + // `GMSStyleSpan` doesn't implement `isEqual` so cannot be compared by value at present. + XCTAssertNotNil(polylineController.polyline.spans); +} + +- (void)testStrokeStylesFromPatterns { + NSArray *> *patterns = @[ @[ @"gap", @10 ], @[ @"dash", @10 ] ]; + UIColor *strokeColor = [UIColor redColor]; + + NSArray *patternStrokeStyle = + [FLTGoogleMapJSONConversions strokeStylesFromPatterns:patterns strokeColor:strokeColor]; + + XCTAssertEqual([patternStrokeStyle count], 2); + + // None of the parameters of `patternStrokeStyle` is observable, so we limit to testing + // the length of this output array. +} + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml index a0b172a65e1..0c50df87f12 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios14/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../../ - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 maps_example_dart: path: ../shared/maps_example_dart/ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/README.md b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/README.md new file mode 100644 index 00000000000..4101b085648 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/README.md @@ -0,0 +1,13 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. + +## Versions + +This example requires iOS 15, so will select a 9.x GoogleMaps SDK version. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/2.0x/red_square.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/2.0x/red_square.png new file mode 100644 index 00000000000..0f82237796b Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/2.0x/red_square.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/3.0x/red_square.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/3.0x/red_square.png new file mode 100644 index 00000000000..7e2739974e7 Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/3.0x/red_square.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/night_mode.json b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/night_mode.json new file mode 100644 index 00000000000..1f16e003a92 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/night_mode.json @@ -0,0 +1,162 @@ +[ + { + "elementType": "geometry", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "featureType": "administrative.locality", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [ + { + "color": "#263c3f" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#6b9a76" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [ + { + "color": "#38414e" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#212a37" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9ca5b3" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#1f2835" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#f3d19c" + } + ] + }, + { + "featureType": "transit", + "elementType": "geometry", + "stylers": [ + { + "color": "#2f3948" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#17263c" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#515c6d" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#17263c" + } + ] + } +] + diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/red_square.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/red_square.png new file mode 100644 index 00000000000..650a2dee711 Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/assets/red_square.png differ diff --git a/packages/dynamic_layouts/example/ios/Flutter/AppFrameworkInfo.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/AppFrameworkInfo.plist similarity index 89% rename from packages/dynamic_layouts/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/AppFrameworkInfo.plist index 7c569640062..b3aaa733dfb 100644 --- a/packages/dynamic_layouts/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/AppFrameworkInfo.plist @@ -20,6 +20,10 @@ ???? CFBundleVersion 1.0 + UIRequiredDeviceCapabilities + + arm64 + MinimumOSVersion 12.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/Debug.xcconfig b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000000..e8efba11468 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/Release.xcconfig b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000000..399e9340e6f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Podfile b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Podfile new file mode 100644 index 00000000000..833aacf78d3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '15.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + + pod 'OCMock', '~> 3.9.1' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/project.pbxproj similarity index 50% rename from packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/project.pbxproj index 3ffa6ecd866..dc2ad2532b9 100644 --- a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/project.pbxproj @@ -9,12 +9,29 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; + 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68E472692836FF0C00BDDDAC /* MapKit.framework */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; }; + F269303B2BB389BF00BF17C4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = F269303A2BB389BF00BF17C4 /* assets */; }; + F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; }; + FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,16 +49,30 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 68E472692836FF0C00BDDDAC /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; + 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PartiallyMockedMapView.h; sourceTree = ""; }; + 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PartiallyMockedMapView.m; sourceTree = ""; }; + B7AFC65E3DD5AC60D834D83D /* 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 = ""; }; + E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F269303A2BB389BF00BF17C4 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = ""; }; + F7151F10265D7ED70028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsTests.m; sourceTree = ""; }; + F7151F14265D7ED70028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +80,32 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F7151F0D265D7ED70028CB91 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */, + FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1E7CF0857EFC88FC263CF3B2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 68E472692836FF0C00BDDDAC /* MapKit.framework */, + 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */, + F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -71,7 +122,10 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + F7151F11265D7ED70028CB91 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, + A189CFE5474BF8A07908B2E0 /* Pods */, + 1E7CF0857EFC88FC263CF3B2 /* Frameworks */, ); sourceTree = ""; }; @@ -79,6 +133,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + F7151F10265D7ED70028CB91 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -86,18 +141,50 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + A189CFE5474BF8A07908B2E0 /* Pods */ = { + isa = PBXGroup; + children = ( + B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */, + EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */, + E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */, + 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + F7151F11265D7ED70028CB91 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + F269303A2BB389BF00BF17C4 /* assets */, + F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */, + 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */, + 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */, + F7151F14265D7ED70028CB91 /* Info.plist */, + ); + path = RunnerTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -105,12 +192,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -121,23 +210,46 @@ productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + F7151F0F265D7ED70028CB91 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */, + F7151F0C265D7ED70028CB91 /* Sources */, + F7151F0D265D7ED70028CB91 /* Frameworks */, + F7151F0E265D7ED70028CB91 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F7151F16265D7ED70028CB91 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = F7151F10265D7ED70028CB91 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; - ORGANIZATIONNAME = ""; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; + }; + F7151F0F265D7ED70028CB91 = { + CreatedOnToolsVersion = 12.5; + ProvisioningStyle = Automatic; + TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -150,6 +262,7 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + F7151F0F265D7ED70028CB91 /* RunnerTests */, ); }; /* End PBXProject section */ @@ -166,6 +279,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F7151F0E265D7ED70028CB91 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F269303B2BB389BF00BF17C4 /* assets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -185,6 +306,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -200,6 +339,48 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/google_maps_flutter_ios/google_maps_flutter_ios_privacy.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/google_maps_flutter_ios_privacy.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -207,13 +388,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + F7151F0C265D7ED70028CB91 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */, + 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + F7151F16265D7ED70028CB91 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -234,81 +433,11 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -328,6 +457,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -352,7 +482,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -364,6 +494,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -383,6 +514,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -401,12 +533,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -417,20 +546,22 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -439,19 +570,62 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + F7151F17265D7ED70028CB91 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + INFOPLIST_FILE = RunnerTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; + }; + name = Debug; + }; + F7151F18265D7ED70028CB91 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + INFOPLIST_FILE = RunnerTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; @@ -463,7 +637,6 @@ buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -473,7 +646,15 @@ buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F7151F17265D7ED70028CB91 /* Debug */, + F7151F18265D7ED70028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/platform/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 70% rename from packages/platform/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09bea..b9b0e81d05f 100644 --- a/packages/platform/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + + skipped = "NO"> + + + + + + diff --git a/packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.h b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/AppDelegate.h similarity index 74% rename from packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.h rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/AppDelegate.h index ca7ca56f3bd..9bc6c56e34f 100644 --- a/packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/AppDelegate.h @@ -3,6 +3,7 @@ // found in the LICENSE file. #import +#import -@interface FFSFileSelectorPlugin : NSObject +@interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/AppDelegate.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/AppDelegate.m new file mode 100644 index 00000000000..55733442b4c --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/AppDelegate.m @@ -0,0 +1,27 @@ +// 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 "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@import GoogleMaps; + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Provide the GoogleMaps API key. + NSString *mapsApiKey = [[NSProcessInfo processInfo] environment][@"MAPS_API_KEY"]; + if ([mapsApiKey length] == 0) { + mapsApiKey = @"YOUR KEY HERE"; + } + [GMSServices provideAPIKey:mapsApiKey]; + + // Register Flutter plugins. + [GeneratedPluginRegistrant registerWithRegistry:self]; + + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000000..3d43d11e66f Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/dynamic_layouts/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/dynamic_layouts/example/ios/Runner/Base.lproj/Main.storyboard b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/dynamic_layouts/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/platform/example/ios/Runner/Info.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Info.plist similarity index 80% rename from packages/platform/example/ios/Runner/Info.plist rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Info.plist index 4a736ea3b6b..6783ca935f1 100644 --- a/packages/platform/example/ios/Runner/Info.plist +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/Info.plist @@ -3,9 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Plaform Example + en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,21 +11,27 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - plaform_example + google_maps_flutter_example CFBundlePackageType APPL CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) + 1.0 CFBundleSignature ???? CFBundleVersion - $(FLUTTER_BUILD_NUMBER) + 1 LSRequiresIPhoneOS + NSLocationWhenInUseUsageDescription + This app needs your location to test the location feature of the Google Maps plugin. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main + UIRequiredDeviceCapabilities + + arm64 + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/main.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/main.m new file mode 100644 index 00000000000..f143297b30d --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/Runner/main.m @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import +#import "AppDelegate.h" + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/GoogleMapsTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/GoogleMapsTests.m new file mode 100644 index 00000000000..b3ac89e2441 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/GoogleMapsTests.m @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import google_maps_flutter_ios; +@import google_maps_flutter_ios.Test; +@import XCTest; +@import GoogleMaps; + +#import +#import "PartiallyMockedMapView.h" + +@interface FLTGoogleMapFactory (Test) +@property(strong, nonatomic, readonly) id sharedMapServices; +@end + +@interface GoogleMapsTests : XCTestCase +@end + +@interface FLTTileProviderController (Testing) +- (UIImage *)handleResultTile:(nullable UIImage *)tileImage; +@end + +@implementation GoogleMapsTests + +- (void)testPlugin { + FLTGoogleMapsPlugin *plugin = [[FLTGoogleMapsPlugin alloc] init]; + XCTAssertNotNil(plugin); +} + +- (void)testFrameObserver { + id registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); + CGRect frame = CGRectMake(0, 0, 100, 100); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // TODO(stuartmorgan): Switch to initWithOptions: once we can guarantee we will be using SDK 8.3+. + // That API was only added in 8.3, and Cocoapod caches on some machines may not be up-to-date + // enough to resolve to that yet even when targeting iOS 14+. + PartiallyMockedMapView *mapView = [[PartiallyMockedMapView alloc] + initWithFrame:frame + camera:[[GMSCameraPosition alloc] initWithLatitude:0 longitude:0 zoom:0]]; +#pragma clang diagnostic pop + FLTGoogleMapController *controller = [[FLTGoogleMapController alloc] initWithMapView:mapView + viewIdentifier:0 + arguments:nil + registrar:registrar]; + + for (NSInteger i = 0; i < 10; ++i) { + [controller view]; + } + XCTAssertEqual(mapView.frameObserverCount, 1); + + mapView.frame = frame; + XCTAssertEqual(mapView.frameObserverCount, 0); +} + +- (void)testMapsServiceSync { + id registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); + FLTGoogleMapFactory *factory1 = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar]; + XCTAssertNotNil(factory1.sharedMapServices); + FLTGoogleMapFactory *factory2 = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar]; + // Test pointer equality, should be same retained singleton +[GMSServices sharedServices] object. + // Retaining the opaque object should be enough to avoid multiple internal initializations, + // but don't test the internals of the GoogleMaps API. Assume that it does what is documented. + // https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_services#a436e03c32b1c0be74e072310a7158831 + XCTAssertEqual(factory1.sharedMapServices, factory2.sharedMapServices); +} + +- (void)testHandleResultTileDownsamplesWideGamutImages { + FLTTileProviderController *controller = [[FLTTileProviderController alloc] init]; + + NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"widegamut" + ofType:@"png" + inDirectory:@"assets"]; + UIImage *wideGamutImage = [UIImage imageWithContentsOfFile:imagePath]; + + XCTAssertNotNil(wideGamutImage, @"The image should be loaded."); + + UIImage *downsampledImage = [controller handleResultTile:wideGamutImage]; + + CGImageRef imageRef = downsampledImage.CGImage; + size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); + + // non wide gamut images use 8 bit format + XCTAssert(bitsPerComponent == 8); +} + +@end diff --git a/packages/platform/example/macos/Runner/Info.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/Info.plist similarity index 60% rename from packages/platform/example/macos/Runner/Info.plist rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/Info.plist index 4789daa6a44..64d65ca4957 100644 --- a/packages/platform/example/macos/Runner/Info.plist +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/Info.plist @@ -6,8 +6,6 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -15,18 +13,10 @@ CFBundleName $(PRODUCT_NAME) CFBundlePackageType - APPL + $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) + 1.0 CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication + 1 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/PartiallyMockedMapView.h b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/PartiallyMockedMapView.h new file mode 100644 index 00000000000..4288401cf90 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/PartiallyMockedMapView.h @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import GoogleMaps; + +/** + * Defines a map view used for testing key-value observing. + */ +@interface PartiallyMockedMapView : GMSMapView + +/** + * The number of times that the `frame` KVO has been added. + */ +@property(nonatomic, assign, readonly) NSInteger frameObserverCount; + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/PartiallyMockedMapView.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/PartiallyMockedMapView.m new file mode 100644 index 00000000000..202a18d128c --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/PartiallyMockedMapView.m @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "PartiallyMockedMapView.h" + +@interface PartiallyMockedMapView () + +@property(nonatomic, assign) NSInteger frameObserverCount; + +@end + +@implementation PartiallyMockedMapView + +- (void)addObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + [super addObserver:observer forKeyPath:keyPath options:options context:context]; + + if ([keyPath isEqualToString:@"frame"]) { + ++self.frameObserverCount; + } +} + +- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath { + [super removeObserver:observer forKeyPath:keyPath]; + + if ([keyPath isEqualToString:@"frame"]) { + --self.frameObserverCount; + } +} + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/assets/widegamut.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/assets/widegamut.png new file mode 100644 index 00000000000..32f032bae3d Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/ios/RunnerTests/assets/widegamut.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/lib/main.dart new file mode 100644 index 00000000000..09fa814fdcf --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/lib/main.dart @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:maps_example_dart/animate_camera.dart'; +import 'package:maps_example_dart/lite_mode.dart'; +import 'package:maps_example_dart/map_click.dart'; +import 'package:maps_example_dart/map_coordinates.dart'; +import 'package:maps_example_dart/map_map_id.dart'; +import 'package:maps_example_dart/map_ui.dart'; +import 'package:maps_example_dart/maps_demo.dart'; +import 'package:maps_example_dart/marker_icons.dart'; +import 'package:maps_example_dart/move_camera.dart'; +import 'package:maps_example_dart/padding.dart'; +import 'package:maps_example_dart/page.dart'; +import 'package:maps_example_dart/place_circle.dart'; +import 'package:maps_example_dart/place_marker.dart'; +import 'package:maps_example_dart/place_polygon.dart'; +import 'package:maps_example_dart/place_polyline.dart'; +import 'package:maps_example_dart/scrolling_map.dart'; +import 'package:maps_example_dart/snapshot.dart'; +import 'package:maps_example_dart/tile_overlay.dart'; + +void main() { + runApp(const MaterialApp( + home: MapsDemo([ + MapUiPage(), + MapCoordinatesPage(), + MapClickPage(), + AnimateCameraPage(), + MoveCameraPage(), + PlaceMarkerPage(), + MarkerIconsPage(), + ScrollingMapPage(), + PlacePolylinePage(), + PlacePolygonPage(), + PlaceCirclePage(), + PaddingPage(), + SnapshotPage(), + LiteModePage(), + TileOverlayPage(), + MapIdPage(), + ]))); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/pubspec.yaml new file mode 100644 index 00000000000..0c50df87f12 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios15/pubspec.yaml @@ -0,0 +1,34 @@ +name: google_maps_flutter_example +description: Demonstrates how to use the google_maps_flutter plugin. +publish_to: none + +environment: + sdk: ^3.2.3 + flutter: ">=3.16.6" + +dependencies: + cupertino_icons: ^1.0.5 + flutter: + sdk: flutter + flutter_plugin_android_lifecycle: ^2.0.1 + google_maps_flutter_ios: + # When depending on this package from a real application you should use: + # google_maps_flutter_ios: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../../ + google_maps_flutter_platform_interface: ^2.7.0 + maps_example_dart: + path: ../shared/maps_example_dart/ + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true + assets: + - assets/ diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/custom_marker_icon.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/custom_marker_icon.dart new file mode 100644 index 00000000000..8940762f02e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/custom_marker_icon.dart @@ -0,0 +1,56 @@ +// 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:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +/// Returns a generated png image in [ByteData] format with the requested size. +Future createCustomMarkerIconImage({required Size size}) async { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final _MarkerPainter painter = _MarkerPainter(); + + painter.paint(canvas, size); + + final ui.Image image = await recorder + .endRecording() + .toImage(size.width.floor(), size.height.floor()); + + final ByteData? bytes = + await image.toByteData(format: ui.ImageByteFormat.png); + return bytes!; +} + +class _MarkerPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + const RadialGradient gradient = RadialGradient( + colors: [Colors.yellow, Colors.red], + stops: [0.4, 1.0], + ); + + // Draw radial gradient + canvas.drawRect( + rect, + Paint()..shader = gradient.createShader(rect), + ); + + // Draw diagonal black line + canvas.drawLine( + Offset.zero, + Offset(size.width, size.height), + Paint() + ..color = Colors.black + ..strokeWidth = 1, + ); + } + + @override + bool shouldRepaint(_MarkerPainter oldDelegate) => false; + @override + bool shouldRebuildSemantics(_MarkerPainter oldDelegate) => false; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/marker_icons.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/marker_icons.dart index 174055613a9..df4f79205e8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/marker_icons.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/marker_icons.dart @@ -5,9 +5,13 @@ // ignore_for_file: public_member_api_docs // ignore_for_file: unawaited_futures +import 'dart:async'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'custom_marker_icon.dart'; import 'example_google_map.dart'; import 'page.dart'; @@ -30,66 +34,303 @@ class MarkerIconsBody extends StatefulWidget { const LatLng _kMapCenter = LatLng(52.4478, -3.5402); +enum _MarkerSizeOption { + original, + width30, + height40, + size30x60, + size120x60, +} + class MarkerIconsBodyState extends State { + final Size _markerAssetImageSize = const Size(48, 48); + _MarkerSizeOption _currentSizeOption = _MarkerSizeOption.original; + Set _markers = {}; + bool _scalingEnabled = true; + bool _mipMapsEnabled = true; ExampleGoogleMapController? controller; - BitmapDescriptor? _markerIcon; + AssetMapBitmap? _markerIconAsset; + BytesMapBitmap? _markerIconBytes; + final int _markersAmountPerType = 15; + bool get _customSizeEnabled => + _currentSizeOption != _MarkerSizeOption.original; @override Widget build(BuildContext context) { - _createMarkerImageFromAsset(context); + _createCustomMarkerIconImages(context); + final Size referenceSize = _getMarkerReferenceSize(); return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Center( - child: SizedBox( - width: 350.0, - height: 300.0, - child: ExampleGoogleMap( - initialCameraPosition: const CameraPosition( - target: _kMapCenter, - zoom: 7.0, + Column(children: [ + Center( + child: SizedBox( + width: 350.0, + height: 300.0, + child: ExampleGoogleMap( + initialCameraPosition: const CameraPosition( + target: _kMapCenter, + zoom: 7.0, + ), + markers: _markers, + onMapCreated: _onMapCreated, + ), + ), + ), + TextButton( + onPressed: () => _toggleScaling(context), + child: Text(_scalingEnabled + ? 'Disable auto scaling' + : 'Enable auto scaling'), + ), + if (_scalingEnabled) ...[ + Container( + width: referenceSize.width, + height: referenceSize.height, + decoration: BoxDecoration( + border: Border.all(), ), - markers: {_createMarker()}, - onMapCreated: _onMapCreated, ), + Text( + 'Reference box with size of ${referenceSize.width} x ${referenceSize.height} in logical pixels.'), + const SizedBox(height: 10), + Image.asset( + 'assets/red_square.png', + scale: _mipMapsEnabled ? null : 1.0, + ), + const Text('Asset image rendered with flutter'), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Marker size:'), + const SizedBox(width: 10), + DropdownButton<_MarkerSizeOption>( + value: _currentSizeOption, + onChanged: (_MarkerSizeOption? newValue) { + if (newValue != null) { + setState(() { + _currentSizeOption = newValue; + _updateMarkerImages(context); + }); + } + }, + items: + _MarkerSizeOption.values.map((_MarkerSizeOption option) { + return DropdownMenuItem<_MarkerSizeOption>( + value: option, + child: Text(_getMarkerSizeOptionName(option)), + ); + }).toList(), + ) + ], + ), + ], + TextButton( + onPressed: () => _toggleMipMaps(context), + child: Text(_mipMapsEnabled ? 'Disable mipmaps' : 'Enable mipmaps'), ), - ) + ]) ], ); } - Marker _createMarker() { - if (_markerIcon != null) { - return Marker( - markerId: const MarkerId('marker_1'), - position: _kMapCenter, - icon: _markerIcon!, - ); + String _getMarkerSizeOptionName(_MarkerSizeOption option) { + switch (option) { + case _MarkerSizeOption.original: + return 'Original'; + case _MarkerSizeOption.width30: + return 'Width 30'; + case _MarkerSizeOption.height40: + return 'Height 40'; + case _MarkerSizeOption.size30x60: + return '30x60'; + case _MarkerSizeOption.size120x60: + return '120x60'; + } + } + + (double? width, double? height) _getCurrentMarkerSize() { + if (_scalingEnabled) { + switch (_currentSizeOption) { + case _MarkerSizeOption.width30: + return (30, null); + case _MarkerSizeOption.height40: + return (null, 40); + case _MarkerSizeOption.size30x60: + return (30, 60); + case _MarkerSizeOption.size120x60: + return (120, 60); + case _MarkerSizeOption.original: + return (_markerAssetImageSize.width, _markerAssetImageSize.height); + } } else { - return const Marker( - markerId: MarkerId('marker_1'), - position: _kMapCenter, - ); + return (_markerAssetImageSize.width, _markerAssetImageSize.height); } } - Future _createMarkerImageFromAsset(BuildContext context) async { - if (_markerIcon == null) { - final ImageConfiguration imageConfiguration = - createLocalImageConfiguration(context, size: const Size.square(48)); - BitmapDescriptor.fromAssetImage( - imageConfiguration, 'assets/red_square.png') - .then(_updateBitmap); + // Helper method to calculate reference size for custom marker size. + Size _getMarkerReferenceSize() { + final (double? width, double? height) = _getCurrentMarkerSize(); + + // Calculates reference size using _markerAssetImageSize aspect ration: + + if (width != null && height != null) { + return Size(width, height); + } else if (width != null) { + return Size(width, + width * _markerAssetImageSize.height / _markerAssetImageSize.width); + } else if (height != null) { + return Size( + height * _markerAssetImageSize.width / _markerAssetImageSize.height, + height); + } else { + return _markerAssetImageSize; } } - void _updateBitmap(BitmapDescriptor bitmap) { + void _toggleMipMaps(BuildContext context) { + _mipMapsEnabled = !_mipMapsEnabled; + _updateMarkerImages(context); + } + + void _toggleScaling(BuildContext context) { + _scalingEnabled = !_scalingEnabled; + _updateMarkerImages(context); + } + + void _updateMarkerImages(BuildContext context) { + _updateMarkerAssetImage(context); + _updateMarkerBytesImage(context); + _updateMarkers(); + } + + Marker _createAssetMarker(int index) { + final LatLng position = + LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude - 1); + + return Marker( + markerId: MarkerId('marker_asset_$index'), + position: position, + icon: _markerIconAsset!, + ); + } + + Marker _createBytesMarker(int index) { + final LatLng position = + LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude + 1); + + return Marker( + markerId: MarkerId('marker_bytes_$index'), + position: position, + icon: _markerIconBytes!, + ); + } + + void _updateMarkers() { + final Set markers = {}; + for (int i = 0; i < _markersAmountPerType; i++) { + if (_markerIconAsset != null) { + markers.add(_createAssetMarker(i)); + } + if (_markerIconBytes != null) { + markers.add(_createBytesMarker(i)); + } + } setState(() { - _markerIcon = bitmap; + _markers = markers; }); } + Future _updateMarkerAssetImage(BuildContext context) async { + // Width and height are used only for custom size. + final (double? width, double? height) = + _scalingEnabled && _customSizeEnabled + ? _getCurrentMarkerSize() + : (null, null); + + AssetMapBitmap assetMapBitmap; + if (_mipMapsEnabled) { + final ImageConfiguration imageConfiguration = + createLocalImageConfiguration( + context, + ); + + assetMapBitmap = await AssetMapBitmap.create( + imageConfiguration, + 'assets/red_square.png', + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none, + ); + } else { + // Uses hardcoded asset path + // This bypasses the asset resolving logic and allows to load the asset + // with precise path. + assetMapBitmap = AssetMapBitmap( + 'assets/red_square.png', + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none, + ); + } + + _updateAssetBitmap(assetMapBitmap); + } + + Future _updateMarkerBytesImage(BuildContext context) async { + final double? devicePixelRatio = + MediaQuery.maybeDevicePixelRatioOf(context); + + final Size bitmapLogicalSize = _getMarkerReferenceSize(); + final double? imagePixelRatio = _scalingEnabled ? devicePixelRatio : null; + + // Create canvasSize with physical marker size + final Size canvasSize = Size( + bitmapLogicalSize.width * (imagePixelRatio ?? 1.0), + bitmapLogicalSize.height * (imagePixelRatio ?? 1.0)); + + final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize); + + // Width and height are used only for custom size. + final (double? width, double? height) = + _scalingEnabled && _customSizeEnabled + ? _getCurrentMarkerSize() + : (null, null); + + final BytesMapBitmap bitmap = BytesMapBitmap(bytes.buffer.asUint8List(), + imagePixelRatio: imagePixelRatio, + width: width, + height: height, + bitmapScaling: + _scalingEnabled ? MapBitmapScaling.auto : MapBitmapScaling.none); + + _updateBytesBitmap(bitmap); + } + + void _updateAssetBitmap(AssetMapBitmap bitmap) { + _markerIconAsset = bitmap; + _updateMarkers(); + } + + void _updateBytesBitmap(BytesMapBitmap bitmap) { + _markerIconBytes = bitmap; + _updateMarkers(); + } + + void _createCustomMarkerIconImages(BuildContext context) { + if (_markerIconAsset == null) { + _updateMarkerAssetImage(context); + } + + if (_markerIconBytes == null) { + _updateMarkerBytesImage(context); + } + } + void _onMapCreated(ExampleGoogleMapController controllerParam) { setState(() { controller = controllerParam; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_marker.dart index 1dd1f6f35dd..d282602d8dc 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_marker.dart @@ -7,11 +7,11 @@ import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'custom_marker_icon.dart'; import 'example_google_map.dart'; import 'page.dart'; @@ -286,26 +286,10 @@ class PlaceMarkerBodyState extends State { }); } - Future _getAssetIcon(BuildContext context) async { - final Completer bitmapIcon = - Completer(); - final ImageConfiguration config = createLocalImageConfiguration(context); - - const AssetImage('assets/red_square.png') - .resolve(config) - .addListener(ImageStreamListener((ImageInfo image, bool sync) async { - final ByteData? bytes = - await image.image.toByteData(format: ImageByteFormat.png); - if (bytes == null) { - bitmapIcon.completeError(Exception('Unable to encode icon')); - return; - } - final BitmapDescriptor bitmap = - BitmapDescriptor.fromBytes(bytes.buffer.asUint8List()); - bitmapIcon.complete(bitmap); - })); - - return bitmapIcon.future; + Future _getMarkerIcon(BuildContext context) async { + const Size canvasSize = Size(48, 48); + final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize); + return BytesMapBitmap(bytes.buffer.asUint8List()); } @override @@ -402,7 +386,7 @@ class PlaceMarkerBodyState extends State { onPressed: selectedId == null ? null : () { - _getAssetIcon(context).then( + _getMarkerIcon(context).then( (BitmapDescriptor icon) { _setMarkerIcon(selectedId, icon); }, diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_polyline.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_polyline.dart index 659ef87e87f..d008b7d3b97 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_polyline.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/lib/place_polyline.dart @@ -292,10 +292,10 @@ class PlacePolylineBodyState extends State { child: const Text('change joint type [Android only]'), ), TextButton( - onPressed: isIOS || (selectedId == null) + onPressed: (selectedId == null) ? null : () => _changePattern(selectedId), - child: const Text('change pattern [Android only]'), + child: const Text('change pattern'), ), ], ) diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml index 77063ec0006..810adc8ee21 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/shared/maps_example_dart/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../../../ - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 dev_dependencies: flutter_test: diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h index cfccb7b0b5f..c6f9fc76eba 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h @@ -7,6 +7,9 @@ NS_ASSUME_NONNULL_BEGIN +/// Returns dict[key], or nil if dict[key] is NSNull. +extern id _Nullable FGMGetValueOrNilFromDict(NSDictionary *dict, NSString *key); + @interface FLTGoogleMapJSONConversions : NSObject + (CLLocationCoordinate2D)locationFromLatLong:(NSArray *)latlong; @@ -25,6 +28,21 @@ NS_ASSUME_NONNULL_BEGIN + (GMSMapViewType)mapViewTypeFromTypeValue:(NSNumber *)value; + (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelValue; +/// Return GMS strokestyle object array populated using the patterns and stroke colors passed in. +/// +/// @param patterns An array of patterns for each stroke in the polyline. +/// @param strokeColor An array of color for each stroke in the polyline. +/// @return An array of GMSStrokeStyle. ++ (NSArray *)strokeStylesFromPatterns:(NSArray *> *)patterns + strokeColor:(UIColor *)strokeColor; + +/// Return GMS strokestyle object array populated using the patterns and stroke colors passed in. +/// Extracts the lengths of each stroke in the polyline from patterns input +/// +/// @param patterns An array of object representing the pattern params in the polyline. +/// @return Array of lengths. ++ (NSArray *)spanLengthsFromPatterns:(NSArray *> *)patterns; + @end NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m index d554c501b1e..f6ea7690222 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m @@ -4,6 +4,12 @@ #import "FLTGoogleMapJSONConversions.h" +/// Returns dict[key], or nil if dict[key] is NSNull. +id FGMGetValueOrNilFromDict(NSDictionary *dict, NSString *key) { + id value = dict[key]; + return value == [NSNull null] ? nil : value; +} + @implementation FLTGoogleMapJSONConversions + (CLLocationCoordinate2D)locationFromLatLong:(NSArray *)latlong { @@ -141,4 +147,26 @@ + (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelVal } return nil; } + ++ (NSArray *)strokeStylesFromPatterns:(NSArray *> *)patterns + strokeColor:(UIColor *)strokeColor { + NSMutableArray *strokeStyles = [[NSMutableArray alloc] initWithCapacity:[patterns count]]; + for (NSArray *pattern in patterns) { + NSString *patternType = pattern[0]; + UIColor *color = [patternType isEqualToString:@"gap"] ? [UIColor clearColor] : strokeColor; + [strokeStyles addObject:[GMSStrokeStyle solidColor:color]]; + } + + return strokeStyles; +} + ++ (NSArray *)spanLengthsFromPatterns:(NSArray *> *)patterns { + NSMutableArray *lengths = [[NSMutableArray alloc] initWithCapacity:[patterns count]]; + for (NSArray *pattern in patterns) { + NSNumber *length = [pattern count] > 1 ? pattern[1] : @0; + [lengths addObject:length]; + } + + return lengths; +} @end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m index 73eab6c1ead..bc56671f5cd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m @@ -70,28 +70,28 @@ - (void)interpretTileOverlayOptions:(NSDictionary *)data { if (!data) { return; } - NSNumber *visible = data[@"visible"]; - if (visible != nil && visible != (id)[NSNull null]) { + NSNumber *visible = FGMGetValueOrNilFromDict(data, @"visible"); + if (visible) { [self setVisible:visible.boolValue]; } - NSNumber *transparency = data[@"transparency"]; - if (transparency != nil && transparency != (id)[NSNull null]) { + NSNumber *transparency = FGMGetValueOrNilFromDict(data, @"transparency"); + if (transparency) { [self setTransparency:transparency.floatValue]; } - NSNumber *zIndex = data[@"zIndex"]; - if (zIndex != nil && zIndex != (id)[NSNull null]) { + NSNumber *zIndex = FGMGetValueOrNilFromDict(data, @"zIndex"); + if (zIndex) { [self setZIndex:zIndex.intValue]; } - NSNumber *fadeIn = data[@"fadeIn"]; - if (fadeIn != nil && fadeIn != (id)[NSNull null]) { + NSNumber *fadeIn = FGMGetValueOrNilFromDict(data, @"fadeIn"); + if (fadeIn) { [self setFadeIn:fadeIn.boolValue]; } - NSNumber *tileSize = data[@"tileSize"]; - if (tileSize != nil && tileSize != (id)[NSNull null]) { + NSNumber *tileSize = FGMGetValueOrNilFromDict(data, @"tileSize"); + if (tileSize) { [self setTileSize:tileSize.integerValue]; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m index 53bf69075c9..1eb74a19559 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m @@ -60,43 +60,43 @@ - (void)setFillColor:(UIColor *)color { } - (void)interpretCircleOptions:(NSDictionary *)data { - NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { + NSNumber *consumeTapEvents = FGMGetValueOrNilFromDict(data, @"consumeTapEvents"); + if (consumeTapEvents) { [self setConsumeTapEvents:consumeTapEvents.boolValue]; } - NSNumber *visible = data[@"visible"]; - if (visible && visible != (id)[NSNull null]) { + NSNumber *visible = FGMGetValueOrNilFromDict(data, @"visible"); + if (visible) { [self setVisible:[visible boolValue]]; } - NSNumber *zIndex = data[@"zIndex"]; - if (zIndex && zIndex != (id)[NSNull null]) { + NSNumber *zIndex = FGMGetValueOrNilFromDict(data, @"zIndex"); + if (zIndex) { [self setZIndex:[zIndex intValue]]; } - NSArray *center = data[@"center"]; - if (center && center != (id)[NSNull null]) { + NSArray *center = FGMGetValueOrNilFromDict(data, @"center"); + if (center) { [self setCenter:[FLTGoogleMapJSONConversions locationFromLatLong:center]]; } - NSNumber *radius = data[@"radius"]; - if (radius && radius != (id)[NSNull null]) { + NSNumber *radius = FGMGetValueOrNilFromDict(data, @"radius"); + if (radius) { [self setRadius:[radius floatValue]]; } - NSNumber *strokeColor = data[@"strokeColor"]; - if (strokeColor && strokeColor != (id)[NSNull null]) { + NSNumber *strokeColor = FGMGetValueOrNilFromDict(data, @"strokeColor"); + if (strokeColor) { [self setStrokeColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]]; } - NSNumber *strokeWidth = data[@"strokeWidth"]; - if (strokeWidth && strokeWidth != (id)[NSNull null]) { + NSNumber *strokeWidth = FGMGetValueOrNilFromDict(data, @"strokeWidth"); + if (strokeWidth) { [self setStrokeWidth:[strokeWidth intValue]]; } - NSNumber *fillColor = data[@"fillColor"]; - if (fillColor && fillColor != (id)[NSNull null]) { + NSNumber *fillColor = FGMGetValueOrNilFromDict(data, @"fillColor"); + if (fillColor) { [self setFillColor:[FLTGoogleMapJSONConversions colorFromRGBA:fillColor]]; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m index 4c747f3eeba..08fdf8cc7f4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m @@ -581,41 +581,41 @@ - (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordi } - (void)interpretMapOptions:(NSDictionary *)data { - NSArray *cameraTargetBounds = data[@"cameraTargetBounds"]; - if (cameraTargetBounds && cameraTargetBounds != (id)[NSNull null]) { + NSArray *cameraTargetBounds = FGMGetValueOrNilFromDict(data, @"cameraTargetBounds"); + if (cameraTargetBounds) { [self setCameraTargetBounds:cameraTargetBounds.count > 0 && cameraTargetBounds[0] != [NSNull null] ? [FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:cameraTargetBounds.firstObject] : nil]; } - NSNumber *compassEnabled = data[@"compassEnabled"]; - if (compassEnabled && compassEnabled != (id)[NSNull null]) { + NSNumber *compassEnabled = FGMGetValueOrNilFromDict(data, @"compassEnabled"); + if (compassEnabled) { [self setCompassEnabled:[compassEnabled boolValue]]; } - id indoorEnabled = data[@"indoorEnabled"]; - if (indoorEnabled && indoorEnabled != [NSNull null]) { + id indoorEnabled = FGMGetValueOrNilFromDict(data, @"indoorEnabled"); + if (indoorEnabled) { [self setIndoorEnabled:[indoorEnabled boolValue]]; } - id trafficEnabled = data[@"trafficEnabled"]; - if (trafficEnabled && trafficEnabled != [NSNull null]) { + id trafficEnabled = FGMGetValueOrNilFromDict(data, @"trafficEnabled"); + if (trafficEnabled) { [self setTrafficEnabled:[trafficEnabled boolValue]]; } - id buildingsEnabled = data[@"buildingsEnabled"]; - if (buildingsEnabled && buildingsEnabled != [NSNull null]) { + id buildingsEnabled = FGMGetValueOrNilFromDict(data, @"buildingsEnabled"); + if (buildingsEnabled) { [self setBuildingsEnabled:[buildingsEnabled boolValue]]; } - id mapType = data[@"mapType"]; - if (mapType && mapType != [NSNull null]) { + id mapType = FGMGetValueOrNilFromDict(data, @"mapType"); + if (mapType) { [self setMapType:[FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:mapType]]; } - NSArray *zoomData = data[@"minMaxZoomPreference"]; - if (zoomData && zoomData != (id)[NSNull null]) { + NSArray *zoomData = FGMGetValueOrNilFromDict(data, @"minMaxZoomPreference"); + if (zoomData) { float minZoom = (zoomData[0] == [NSNull null]) ? kGMSMinZoomLevel : [zoomData[0] floatValue]; float maxZoom = (zoomData[1] == [NSNull null]) ? kGMSMaxZoomLevel : [zoomData[1] floatValue]; [self setMinZoom:minZoom maxZoom:maxZoom]; } - NSArray *paddingData = data[@"padding"]; + NSArray *paddingData = FGMGetValueOrNilFromDict(data, @"padding"); if (paddingData) { float top = (paddingData[0] == [NSNull null]) ? 0 : [paddingData[0] floatValue]; float left = (paddingData[1] == [NSNull null]) ? 0 : [paddingData[1] floatValue]; @@ -624,35 +624,35 @@ - (void)interpretMapOptions:(NSDictionary *)data { [self setPaddingTop:top left:left bottom:bottom right:right]; } - NSNumber *rotateGesturesEnabled = data[@"rotateGesturesEnabled"]; - if (rotateGesturesEnabled && rotateGesturesEnabled != (id)[NSNull null]) { + NSNumber *rotateGesturesEnabled = FGMGetValueOrNilFromDict(data, @"rotateGesturesEnabled"); + if (rotateGesturesEnabled) { [self setRotateGesturesEnabled:[rotateGesturesEnabled boolValue]]; } - NSNumber *scrollGesturesEnabled = data[@"scrollGesturesEnabled"]; - if (scrollGesturesEnabled && scrollGesturesEnabled != (id)[NSNull null]) { + NSNumber *scrollGesturesEnabled = FGMGetValueOrNilFromDict(data, @"scrollGesturesEnabled"); + if (scrollGesturesEnabled) { [self setScrollGesturesEnabled:[scrollGesturesEnabled boolValue]]; } - NSNumber *tiltGesturesEnabled = data[@"tiltGesturesEnabled"]; - if (tiltGesturesEnabled && tiltGesturesEnabled != (id)[NSNull null]) { + NSNumber *tiltGesturesEnabled = FGMGetValueOrNilFromDict(data, @"tiltGesturesEnabled"); + if (tiltGesturesEnabled) { [self setTiltGesturesEnabled:[tiltGesturesEnabled boolValue]]; } - NSNumber *trackCameraPosition = data[@"trackCameraPosition"]; - if (trackCameraPosition && trackCameraPosition != (id)[NSNull null]) { + NSNumber *trackCameraPosition = FGMGetValueOrNilFromDict(data, @"trackCameraPosition"); + if (trackCameraPosition) { [self setTrackCameraPosition:[trackCameraPosition boolValue]]; } - NSNumber *zoomGesturesEnabled = data[@"zoomGesturesEnabled"]; - if (zoomGesturesEnabled && zoomGesturesEnabled != (id)[NSNull null]) { + NSNumber *zoomGesturesEnabled = FGMGetValueOrNilFromDict(data, @"zoomGesturesEnabled"); + if (zoomGesturesEnabled) { [self setZoomGesturesEnabled:[zoomGesturesEnabled boolValue]]; } - NSNumber *myLocationEnabled = data[@"myLocationEnabled"]; - if (myLocationEnabled && myLocationEnabled != (id)[NSNull null]) { + NSNumber *myLocationEnabled = FGMGetValueOrNilFromDict(data, @"myLocationEnabled"); + if (myLocationEnabled) { [self setMyLocationEnabled:[myLocationEnabled boolValue]]; } - NSNumber *myLocationButtonEnabled = data[@"myLocationButtonEnabled"]; - if (myLocationButtonEnabled && myLocationButtonEnabled != (id)[NSNull null]) { + NSNumber *myLocationButtonEnabled = FGMGetValueOrNilFromDict(data, @"myLocationButtonEnabled"); + if (myLocationButtonEnabled) { [self setMyLocationButtonEnabled:[myLocationButtonEnabled boolValue]]; } - NSString *style = data[@"style"]; + NSString *style = FGMGetValueOrNilFromDict(data, @"style"); if (style) { self.styleError = [self setMapStyle:style]; } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m index dd07e791a88..1bc7d2a8db8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m @@ -91,68 +91,71 @@ - (void)setZIndex:(int)zIndex { } - (void)interpretMarkerOptions:(NSDictionary *)data - registrar:(NSObject *)registrar { - NSNumber *alpha = data[@"alpha"]; - if (alpha && alpha != (id)[NSNull null]) { + registrar:(NSObject *)registrar + screenScale:(CGFloat)screenScale { + NSNumber *alpha = FGMGetValueOrNilFromDict(data, @"alpha"); + if (alpha) { [self setAlpha:[alpha floatValue]]; } - NSArray *anchor = data[@"anchor"]; - if (anchor && anchor != (id)[NSNull null]) { + NSArray *anchor = FGMGetValueOrNilFromDict(data, @"anchor"); + if (anchor) { [self setAnchor:[FLTGoogleMapJSONConversions pointFromArray:anchor]]; } - NSNumber *draggable = data[@"draggable"]; - if (draggable && draggable != (id)[NSNull null]) { + NSNumber *draggable = FGMGetValueOrNilFromDict(data, @"draggable"); + if (draggable) { [self setDraggable:[draggable boolValue]]; } - NSArray *icon = data[@"icon"]; - if (icon && icon != (id)[NSNull null]) { - UIImage *image = [self extractIconFromData:icon registrar:registrar]; + NSArray *icon = FGMGetValueOrNilFromDict(data, @"icon"); + if (icon) { + UIImage *image = [self extractIconFromData:icon registrar:registrar screenScale:screenScale]; [self setIcon:image]; } - NSNumber *flat = data[@"flat"]; - if (flat && flat != (id)[NSNull null]) { + NSNumber *flat = FGMGetValueOrNilFromDict(data, @"flat"); + if (flat) { [self setFlat:[flat boolValue]]; } - NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { + NSNumber *consumeTapEvents = FGMGetValueOrNilFromDict(data, @"consumeTapEvents"); + if (consumeTapEvents) { [self setConsumeTapEvents:[consumeTapEvents boolValue]]; } [self interpretInfoWindow:data]; - NSArray *position = data[@"position"]; - if (position && position != (id)[NSNull null]) { + NSArray *position = FGMGetValueOrNilFromDict(data, @"position"); + if (position) { [self setPosition:[FLTGoogleMapJSONConversions locationFromLatLong:position]]; } - NSNumber *rotation = data[@"rotation"]; - if (rotation && rotation != (id)[NSNull null]) { + NSNumber *rotation = FGMGetValueOrNilFromDict(data, @"rotation"); + if (rotation) { [self setRotation:[rotation doubleValue]]; } - NSNumber *visible = data[@"visible"]; - if (visible && visible != (id)[NSNull null]) { + NSNumber *visible = FGMGetValueOrNilFromDict(data, @"visible"); + if (visible) { [self setVisible:[visible boolValue]]; } - NSNumber *zIndex = data[@"zIndex"]; - if (zIndex && zIndex != (id)[NSNull null]) { + NSNumber *zIndex = FGMGetValueOrNilFromDict(data, @"zIndex"); + if (zIndex) { [self setZIndex:[zIndex intValue]]; } } - (void)interpretInfoWindow:(NSDictionary *)data { - NSDictionary *infoWindow = data[@"infoWindow"]; - if (infoWindow && infoWindow != (id)[NSNull null]) { - NSString *title = infoWindow[@"title"]; - NSString *snippet = infoWindow[@"snippet"]; - if (title && title != (id)[NSNull null]) { + NSDictionary *infoWindow = FGMGetValueOrNilFromDict(data, @"infoWindow"); + if (infoWindow) { + NSString *title = FGMGetValueOrNilFromDict(infoWindow, @"title"); + NSString *snippet = FGMGetValueOrNilFromDict(infoWindow, @"snippet"); + if (title) { [self setInfoWindowTitle:title snippet:snippet]; } NSArray *infoWindowAnchor = infoWindow[@"infoWindowAnchor"]; - if (infoWindowAnchor && infoWindowAnchor != (id)[NSNull null]) { + if (infoWindowAnchor) { [self setInfoWindowAnchor:[FLTGoogleMapJSONConversions pointFromArray:infoWindowAnchor]]; } } } - (UIImage *)extractIconFromData:(NSArray *)iconData - registrar:(NSObject *)registrar { + registrar:(NSObject *)registrar + screenScale:(CGFloat)screenScale { + NSAssert(screenScale > 0, @"Screen scale must be greater than 0"); UIImage *image; if ([iconData.firstObject isEqualToString:@"defaultMarker"]) { CGFloat hue = (iconData.count == 1) ? 0.0f : [iconData[1] doubleValue]; @@ -161,6 +164,8 @@ - (UIImage *)extractIconFromData:(NSArray *)iconData brightness:0.7 alpha:1.0]]; } else if ([iconData.firstObject isEqualToString:@"fromAsset"]) { + // Deprecated: This message handling for 'fromAsset' has been replaced by 'asset'. + // Refer to the flutter google_maps_flutter_platform_interface package for details. if (iconData.count == 2) { image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]]; } else { @@ -168,6 +173,8 @@ - (UIImage *)extractIconFromData:(NSArray *)iconData fromPackage:iconData[2]]]; } } else if ([iconData.firstObject isEqualToString:@"fromAssetImage"]) { + // Deprecated: This message handling for 'fromAssetImage' has been replaced by 'asset'. + // Refer to the flutter google_maps_flutter_platform_interface package for details. if (iconData.count == 3) { image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]]; id scaleParam = iconData[2]; @@ -182,11 +189,13 @@ - (UIImage *)extractIconFromData:(NSArray *)iconData @throw exception; } } else if ([iconData[0] isEqualToString:@"fromBytes"]) { + // Deprecated: This message handling for 'fromBytes' has been replaced by 'bytes'. + // Refer to the flutter google_maps_flutter_platform_interface package for details. if (iconData.count == 2) { @try { FlutterStandardTypedData *byteData = iconData[1]; - CGFloat screenScale = [[UIScreen mainScreen] scale]; - image = [UIImage imageWithData:[byteData data] scale:screenScale]; + CGFloat mainScreenScale = [[UIScreen mainScreen] scale]; + image = [UIImage imageWithData:[byteData data] scale:mainScreenScale]; } @catch (NSException *exception) { @throw [NSException exceptionWithName:@"InvalidByteDescriptor" reason:@"Unable to interpret bytes as a valid image." @@ -201,11 +210,88 @@ - (UIImage *)extractIconFromData:(NSArray *)iconData userInfo:nil]; @throw exception; } + } else if ([iconData.firstObject isEqualToString:@"asset"]) { + NSDictionary *assetData = iconData[1]; + if (![assetData isKindOfClass:[NSDictionary class]]) { + NSException *exception = + [NSException exceptionWithName:@"InvalidByteDescriptor" + reason:@"Unable to interpret asset, expected a dictionary as the " + @"second parameter." + userInfo:nil]; + @throw exception; + } + + NSString *assetName = FGMGetValueOrNilFromDict(assetData, @"assetName"); + NSString *scalingMode = FGMGetValueOrNilFromDict(assetData, @"bitmapScaling"); + + image = [UIImage imageNamed:[registrar lookupKeyForAsset:assetName]]; + + if ([scalingMode isEqualToString:@"auto"]) { + NSNumber *width = FGMGetValueOrNilFromDict(assetData, @"width"); + NSNumber *height = FGMGetValueOrNilFromDict(assetData, @"height"); + CGFloat imagePixelRatio = + [FGMGetValueOrNilFromDict(assetData, @"imagePixelRatio") doubleValue]; + + if (width || height) { + image = [FLTGoogleMapMarkerController scaledImage:image withScale:screenScale]; + image = [FLTGoogleMapMarkerController scaledImage:image + withWidth:width + height:height + screenScale:screenScale]; + } else { + image = [FLTGoogleMapMarkerController scaledImage:image withScale:imagePixelRatio]; + } + } + } else if ([iconData[0] isEqualToString:@"bytes"]) { + NSDictionary *byteData = iconData[1]; + if (![byteData isKindOfClass:[NSDictionary class]]) { + NSException *exception = + [NSException exceptionWithName:@"InvalidByteDescriptor" + reason:@"Unable to interpret bytes, expected a dictionary as the " + @"second parameter." + userInfo:nil]; + @throw exception; + } + + FlutterStandardTypedData *bytes = FGMGetValueOrNilFromDict(byteData, @"byteData"); + NSString *scalingMode = FGMGetValueOrNilFromDict(byteData, @"bitmapScaling"); + + @try { + image = [UIImage imageWithData:[bytes data] scale:screenScale]; + if ([scalingMode isEqualToString:@"auto"]) { + NSNumber *width = FGMGetValueOrNilFromDict(byteData, @"width"); + NSNumber *height = FGMGetValueOrNilFromDict(byteData, @"height"); + CGFloat imagePixelRatio = + [FGMGetValueOrNilFromDict(byteData, @"imagePixelRatio") doubleValue]; + + if (width || height) { + // Before scaling the image, image must be in screenScale + image = [FLTGoogleMapMarkerController scaledImage:image withScale:screenScale]; + image = [FLTGoogleMapMarkerController scaledImage:image + withWidth:width + height:height + screenScale:screenScale]; + } else { + image = [FLTGoogleMapMarkerController scaledImage:image withScale:imagePixelRatio]; + } + } else { + // No scaling, load image from bytes without scale parameter. + image = [UIImage imageWithData:[bytes data]]; + } + } @catch (NSException *exception) { + @throw [NSException exceptionWithName:@"InvalidByteDescriptor" + reason:@"Unable to interpret bytes as a valid image." + userInfo:nil]; + } } return image; } +/// This method is deprecated within the context of `BitmapDescriptor.fromBytes` handling in the +/// flutter google_maps_flutter_platform_interface package which has been replaced by 'bytes' +/// message handling. It will be removed when the deprecated image bitmap description type +/// 'fromBytes' is removed from the platform interface. - (UIImage *)scaleImage:(UIImage *)image by:(id)scaleParam { double scale = 1.0; if ([scaleParam isKindOfClass:[NSNumber class]]) { @@ -219,6 +305,127 @@ - (UIImage *)scaleImage:(UIImage *)image by:(id)scaleParam { return image; } +/// Creates a scaled version of the provided UIImage based on a specified scale factor. If the +/// scale factor differs from the image's current scale by more than a small epsilon-delta (to +/// account for minor floating-point inaccuracies), a new UIImage object is created with the +/// specified scale. Otherwise, the original image is returned. +/// +/// @param image The UIImage to scale. +/// @param scale The factor by which to scale the image. +/// @return UIImage Returns the scaled UIImage. ++ (UIImage *)scaledImage:(UIImage *)image withScale:(CGFloat)scale { + if (fabs(scale - image.scale) > DBL_EPSILON) { + return [UIImage imageWithCGImage:[image CGImage] + scale:scale + orientation:(image.imageOrientation)]; + } + return image; +} + +/// Scales an input UIImage to a specified size. If the aspect ratio of the input image +/// closely matches the target size, indicated by a small epsilon-delta, the image's scale +/// property is updated instead of resizing the image. If the aspect ratios differ beyond this +/// threshold, the method redraws the image at the target size. +/// +/// @param image The UIImage to scale. +/// @param size The target CGSize to scale the image to. +/// @return UIImage Returns the scaled UIImage. ++ (UIImage *)scaledImage:(UIImage *)image withSize:(CGSize)size { + CGFloat originalPixelWidth = image.size.width * image.scale; + CGFloat originalPixelHeight = image.size.height * image.scale; + + // Return original image if either original image size or target size is so small that + // image cannot be resized or displayed. + if (originalPixelWidth <= 0 || originalPixelHeight <= 0 || size.width <= 0 || size.height <= 0) { + return image; + } + + // Check if the image's size, accounting for scale, matches the target size. + if (fabs(originalPixelWidth - size.width) <= DBL_EPSILON && + fabs(originalPixelHeight - size.height) <= DBL_EPSILON) { + // No need for resizing, return the original image + return image; + } + + // Check if the aspect ratios are approximately equal. + CGSize originalPixelSize = CGSizeMake(originalPixelWidth, originalPixelHeight); + if ([FLTGoogleMapMarkerController isScalableWithScaleFactorFromSize:originalPixelSize + toSize:size]) { + // Scaled image has close to same aspect ratio, + // updating image scale instead of resizing image. + CGFloat factor = originalPixelWidth / size.width; + return [FLTGoogleMapMarkerController scaledImage:image withScale:(image.scale * factor)]; + } else { + // Aspect ratios differ significantly, resize the image. + UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat]; + format.scale = 1.0; + format.opaque = NO; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size + format:format]; + UIImage *newImage = + [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) { + [image drawInRect:CGRectMake(0, 0, size.width, size.height)]; + }]; + + // Return image with proper scaling. + return [FLTGoogleMapMarkerController scaledImage:newImage withScale:image.scale]; + } +} + +/// Scales an input UIImage to a specified width and height preserving aspect ratio if both +/// widht and height are not given.. +/// +/// @param image The UIImage to scale. +/// @param width The target width to scale the image to. +/// @param height The target height to scale the image to. +/// @param screenScale The current screen scale. +/// @return UIImage Returns the scaled UIImage. ++ (UIImage *)scaledImage:(UIImage *)image + withWidth:(NSNumber *)width + height:(NSNumber *)height + screenScale:(CGFloat)screenScale { + if (!width && !height) { + return image; + } + + CGFloat targetWidth = width ? width.doubleValue : image.size.width; + CGFloat targetHeight = height ? height.doubleValue : image.size.height; + + if (width && !height) { + // Calculate height based on aspect ratio if only width is provided. + double aspectRatio = image.size.height / image.size.width; + targetHeight = round(targetWidth * aspectRatio); + } else if (!width && height) { + // Calculate width based on aspect ratio if only height is provided. + double aspectRatio = image.size.width / image.size.height; + targetWidth = round(targetHeight * aspectRatio); + } + + CGSize targetSize = + CGSizeMake(round(targetWidth * screenScale), round(targetHeight * screenScale)); + return [FLTGoogleMapMarkerController scaledImage:image withSize:targetSize]; +} + ++ (BOOL)isScalableWithScaleFactorFromSize:(CGSize)originalSize toSize:(CGSize)targetSize { + // Select the scaling factor based on the longer side to have good precision. + CGFloat scaleFactor = (originalSize.width > originalSize.height) + ? (targetSize.width / originalSize.width) + : (targetSize.height / originalSize.height); + + // Calculate the scaled dimensions. + CGFloat scaledWidth = originalSize.width * scaleFactor; + CGFloat scaledHeight = originalSize.height * scaleFactor; + + // Check if the scaled dimensions are within a one-pixel + // threshold of the target dimensions. + BOOL widthWithinThreshold = fabs(scaledWidth - targetSize.width) <= 1.0; + BOOL heightWithinThreshold = fabs(scaledHeight - targetSize.height) <= 1.0; + + // The image is considered scalable with scale factor + // if both dimensions are within the threshold. + return widthWithinThreshold && heightWithinThreshold; +} + @end @interface FLTMarkersController () @@ -253,7 +460,9 @@ - (void)addMarkers:(NSArray *)markersToAdd { [[FLTGoogleMapMarkerController alloc] initMarkerWithPosition:position identifier:identifier mapView:self.mapView]; - [controller interpretMarkerOptions:marker registrar:self.registrar]; + [controller interpretMarkerOptions:marker + registrar:self.registrar + screenScale:[self getScreenScale]]; self.markerIdentifierToController[identifier] = controller; } } @@ -265,7 +474,9 @@ - (void)changeMarkers:(NSArray *)markersToChange { if (!controller) { continue; } - [controller interpretMarkerOptions:marker registrar:self.registrar]; + [controller interpretMarkerOptions:marker + registrar:self.registrar + screenScale:[self getScreenScale]]; } } @@ -379,6 +590,16 @@ - (void)isInfoWindowShownForMarkerWithIdentifier:(NSString *)identifier } } +- (CGFloat)getScreenScale { + // TODO(jokerttu): This method is called on marker creation, which, for initial markers, is done + // before the view is added to the view hierarchy. This means that the traitCollection values may + // not be matching the right display where the map is finally shown. The solution should be + // revisited after the proper way to fetch the display scale is resolved for platform views. This + // should be done under the context of the following issue: + // https://github.com/flutter/flutter/issues/125496. + return self.mapView.traitCollection.displayScale; +} + + (CLLocationCoordinate2D)getPosition:(NSDictionary *)marker { NSArray *position = marker[@"position"]; return [FLTGoogleMapJSONConversions locationFromLatLong:position]; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController_Test.h new file mode 100644 index 00000000000..84e6d0937a2 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController_Test.h @@ -0,0 +1,29 @@ +// 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 "GoogleMapMarkerController.h" + +@interface FLTGoogleMapMarkerController (Test) + +/// Extracts an icon image from the iconData array. +/// +/// @param iconData An array containing the data for the icon image. +/// @param registrar A Flutter plugin registrar. +/// @param screenScale Screen scale factor for scaling bitmaps. Must be greater than 0. +/// @return A UIImage object created from the icon data. +/// @note Assert unless screenScale is greater than 0. +- (UIImage *)extractIconFromData:(NSArray *)iconData + registrar:(NSObject *)registrar + screenScale:(CGFloat)screenScale; + +/// Checks if an image can be scaled from an original size to a target size using a scale factor +/// while maintaining the aspect ratio. +/// +/// @param originalSize The original size of the image. +/// @param targetSize The desired target size to scale the image to. +/// @return A BOOL indicating whether the image can be scaled to the target size with scale +/// factor. ++ (BOOL)isScalableWithScaleFactorFromSize:(CGSize)originalSize toSize:(CGSize)targetSize; + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m index 398adfcacec..d8dc47def60 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m @@ -73,43 +73,43 @@ - (void)setStrokeWidth:(CGFloat)width { - (void)interpretPolygonOptions:(NSDictionary *)data registrar:(NSObject *)registrar { - NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { + NSNumber *consumeTapEvents = FGMGetValueOrNilFromDict(data, @"consumeTapEvents"); + if (consumeTapEvents) { [self setConsumeTapEvents:[consumeTapEvents boolValue]]; } - NSNumber *visible = data[@"visible"]; - if (visible && visible != (id)[NSNull null]) { + NSNumber *visible = FGMGetValueOrNilFromDict(data, @"visible"); + if (visible) { [self setVisible:[visible boolValue]]; } - NSNumber *zIndex = data[@"zIndex"]; - if (zIndex && zIndex != (id)[NSNull null]) { + NSNumber *zIndex = FGMGetValueOrNilFromDict(data, @"zIndex"); + if (zIndex) { [self setZIndex:[zIndex intValue]]; } - NSArray *points = data[@"points"]; - if (points && points != (id)[NSNull null]) { + NSArray *points = FGMGetValueOrNilFromDict(data, @"points"); + if (points) { [self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]]; } - NSArray *holes = data[@"holes"]; - if (holes && holes != (id)[NSNull null]) { + NSArray *holes = FGMGetValueOrNilFromDict(data, @"holes"); + if (holes) { [self setHoles:[FLTGoogleMapJSONConversions holesFromPointsArray:holes]]; } - NSNumber *fillColor = data[@"fillColor"]; - if (fillColor && fillColor != (id)[NSNull null]) { + NSNumber *fillColor = FGMGetValueOrNilFromDict(data, @"fillColor"); + if (fillColor) { [self setFillColor:[FLTGoogleMapJSONConversions colorFromRGBA:fillColor]]; } - NSNumber *strokeColor = data[@"strokeColor"]; - if (strokeColor && strokeColor != (id)[NSNull null]) { + NSNumber *strokeColor = FGMGetValueOrNilFromDict(data, @"strokeColor"); + if (strokeColor) { [self setStrokeColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]]; } - NSNumber *strokeWidth = data[@"strokeWidth"]; - if (strokeWidth && strokeWidth != (id)[NSNull null]) { + NSNumber *strokeWidth = FGMGetValueOrNilFromDict(data, @"strokeWidth"); + if (strokeWidth) { [self setStrokeWidth:[strokeWidth intValue]]; } } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h index f85d1a3896f..63061392e5a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h @@ -11,6 +11,12 @@ identifier:(NSString *)identifier mapView:(GMSMapView *)mapView; - (void)removePolyline; + +/// Sets the pattern on polyline controller +/// +/// @param styles The styles for repeating pattern sections. +/// @param lengths The lengths for repeating pattern sections. +- (void)setPattern:(NSArray *)styles lengths:(NSArray *)lengths; @end @interface FLTPolylinesController : NSObject diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m index 77601d4a1bb..a97f372d6fd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m @@ -59,42 +59,54 @@ - (void)setGeodesic:(BOOL)isGeodesic { self.polyline.geodesic = isGeodesic; } +- (void)setPattern:(NSArray *)styles lengths:(NSArray *)lengths { + self.polyline.spans = GMSStyleSpans(self.polyline.path, styles, lengths, kGMSLengthRhumb); +} + - (void)interpretPolylineOptions:(NSDictionary *)data registrar:(NSObject *)registrar { - NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { + NSNumber *consumeTapEvents = FGMGetValueOrNilFromDict(data, @"consumeTapEvents"); + if (consumeTapEvents) { [self setConsumeTapEvents:[consumeTapEvents boolValue]]; } - NSNumber *visible = data[@"visible"]; - if (visible && visible != (id)[NSNull null]) { + NSNumber *visible = FGMGetValueOrNilFromDict(data, @"visible"); + if (visible) { [self setVisible:[visible boolValue]]; } - NSNumber *zIndex = data[@"zIndex"]; - if (zIndex && zIndex != (id)[NSNull null]) { + NSNumber *zIndex = FGMGetValueOrNilFromDict(data, @"zIndex"); + if (zIndex) { [self setZIndex:[zIndex intValue]]; } - NSArray *points = data[@"points"]; - if (points && points != (id)[NSNull null]) { + NSArray *points = FGMGetValueOrNilFromDict(data, @"points"); + if (points) { [self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]]; } - NSNumber *strokeColor = data[@"color"]; - if (strokeColor && strokeColor != (id)[NSNull null]) { + NSNumber *strokeColor = FGMGetValueOrNilFromDict(data, @"color"); + if (strokeColor) { [self setColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]]; } - NSNumber *strokeWidth = data[@"width"]; - if (strokeWidth && strokeWidth != (id)[NSNull null]) { + NSNumber *strokeWidth = FGMGetValueOrNilFromDict(data, @"width"); + if (strokeWidth) { [self setStrokeWidth:[strokeWidth intValue]]; } - NSNumber *geodesic = data[@"geodesic"]; - if (geodesic && geodesic != (id)[NSNull null]) { + NSNumber *geodesic = FGMGetValueOrNilFromDict(data, @"geodesic"); + if (geodesic) { [self setGeodesic:geodesic.boolValue]; } + + NSArray *patterns = FGMGetValueOrNilFromDict(data, @"pattern"); + if (patterns) { + [self + setPattern:[FLTGoogleMapJSONConversions strokeStylesFromPatterns:patterns + strokeColor:self.polyline.strokeColor] + lengths:[FLTGoogleMapJSONConversions spanLengthsFromPatterns:patterns]]; + } } @end @@ -125,7 +137,7 @@ - (instancetype)init:(FlutterMethodChannel *)methodChannel } - (void)addPolylines:(NSArray *)polylinesToAdd { for (NSDictionary *polyline in polylinesToAdd) { - GMSMutablePath *path = [FLTPolylinesController getPath:polyline]; + GMSMutablePath *path = [FLTPolylinesController pathForPolyline:polyline]; NSString *identifier = polyline[@"polylineId"]; FLTGoogleMapPolylineController *controller = [[FLTGoogleMapPolylineController alloc] initPolylineWithPath:path @@ -171,7 +183,7 @@ - (bool)hasPolylineWithIdentifier:(NSString *)identifier { } return self.polylineIdentifierToController[identifier] != nil; } -+ (GMSMutablePath *)getPath:(NSDictionary *)polyline { ++ (GMSMutablePath *)pathForPolyline:(NSDictionary *)polyline { NSArray *pointArray = polyline[@"points"]; NSArray *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:pointArray]; GMSMutablePath *path = [GMSMutablePath path]; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController_Test.h new file mode 100644 index 00000000000..2b6e91e5d12 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController_Test.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "GoogleMapPolylineController.h" + +/// Internal APIs exposed for unit testing +@interface FLTGoogleMapPolylineController (Test) + +/// Polyline instance the controller is attached to +@property(strong, nonatomic) GMSPolyline *polyline; + +@end + +/// Internal APIs explosed for unit testing +@interface FLTPolylinesController (Test) + +/// Returns the path for polyline based on the points(locations) the polyline has. +/// +/// @param polyline The polyline instance for which path is calculated. +/// @return An instance of GMSMutablePath. ++ (GMSMutablePath *)pathForPolyline:(NSDictionary *)polyline; + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios-umbrella.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios-umbrella.h index 791c3aaea6c..4f331de069b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios-umbrella.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios-umbrella.h @@ -6,6 +6,7 @@ #import #import #import +#import FOUNDATION_EXPORT double google_maps_flutterVersionNumber; FOUNDATION_EXPORT const unsigned char google_maps_flutterVersionString[]; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap index 699e6753db3..ad6fe6735b1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap @@ -6,5 +6,6 @@ framework module google_maps_flutter_ios { explicit module Test { header "GoogleMapController_Test.h" + header "GoogleMapMarkerController_Test.h" } } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec index adf0f706089..ce652f3b41b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec @@ -24,7 +24,7 @@ Downloaded by pub (not CocoaPods). # broad as possible. # Versions earlier than 8.4 can't be supported because that's the first version # that supports privacy manifests. - s.dependency 'GoogleMaps', '>= 8.4', '< 9.0' + s.dependency 'GoogleMaps', '>= 8.4', '< 10.0' s.static_framework = true s.platform = :ios, '14.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml index e98f7209380..0314d44e46f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_ios description: iOS implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.6.0 +version: 2.8.1 environment: sdk: ^3.2.3 @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 stream_transform: ^2.0.0 dev_dependencies: diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index 668dcb2731b..a137b29112c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,21 @@ +## 2.8.0 + +* Deprecates `BitmapDescriptor.fromAssetImage` in favor of `BitmapDescriptor.asset` and `AssetMapBitmap.create`. +* Deprecates `BitmapDescriptor.fromBytes` in favor of `BitmapDescriptor.bytes` and `BytesMapBitmap` + +## 2.7.1 + +* Undeprecates `BitmapDescriptor.fromAssetImage`. +* Undeprecates `BitmapDescriptor.fromBytes`. +* Fixes issues with deprecation in version 2.7.0. + +## 2.7.0 + +* Adds better support for marker size and scaling behaviour with `AssetMapBitmap` and `BytesMapBitmap`. +* Deprecates `BitmapDescriptor.fromAssetImage` in favor of `BitmapDescriptor.asset` and `AssetMapBitmap.create`. +* Deprecates `BitmapDescriptor.fromBytes` in favor of `BitmapDescriptor.bytes` and `BytesMapBitmap` +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.6.0 * Adds support for marker clustering. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart index dd728cf1349..2f162bf5046 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart @@ -518,7 +518,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { /// This implementation uses hybrid composition to render the Google Maps /// Widget on Android. This comes at the cost of some performance on Android /// versions below 10. See - /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more + /// https://docs.flutter.dev/platform-integration/android/platform-views#performance for more /// information. /// /// If set to true, the google map widget should be built with diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart index 5a60a202cbb..71b5e267f64 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart @@ -4,16 +4,46 @@ import 'dart:async' show Future; import 'dart:typed_data' show Uint8List; -import 'dart:ui' show Size; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart' - show AssetBundleImageKey, AssetImage, ImageConfiguration; + show + AssetBundleImageKey, + AssetImage, + BoxFit, + ImageConfiguration, + Size, + createLocalImageConfiguration; import 'package:flutter/services.dart' show AssetBundle; +/// Type of bitmap scaling to use on BitmapDescriptor creation. +enum MapBitmapScaling { + /// Automatically scale image with devices pixel ratio or to given size, + /// to keep marker sizes same between platforms and devices. + auto, + + /// Render marker to the map as without scaling. + /// + /// This can be used if the image is already pre-scaled, or to increase + /// performance with a large numbers of markers. + none, +} + +// The default pixel ratio for custom bitmaps. +const double _naturalPixelRatio = 1.0; + /// Defines a bitmap image. For a marker, this class can be used to set the /// image of the marker icon. For a ground overlay, it can be used to set the /// image to place on the surface of the earth. +/// +/// Use the [BitmapDescriptor.asset] or [AssetMapBitmap.create] to create a +/// [BitmapDescriptor] image from an asset. +/// Use the [BitmapDescriptor.bytes] or [BytesMapBitmap] to create a +/// [BitmapDescriptor] image from a list of bytes. +/// Use the [BitmapDescriptor.defaultMarker] to create a [BitmapDescriptor] for +/// a default marker icon. +/// Use the [BitmapDescriptor.defaultMarkerWithHue] to create a +/// [BitmapDescriptor] for a default marker icon with a hue value. class BitmapDescriptor { const BitmapDescriptor._(this._json); @@ -53,12 +83,39 @@ class BitmapDescriptor { assert(jsonList[3] != null && jsonList[3] is List); assert((jsonList[3] as List).length == 2); } + case AssetMapBitmap.type: + assert(jsonList.length == 2); + assert(jsonList[1] != null && jsonList[1] is Map); + final Map jsonMap = + jsonList[1] as Map; + assert(jsonMap.containsKey('assetName')); + assert(jsonMap.containsKey('bitmapScaling')); + assert(jsonMap.containsKey('imagePixelRatio')); + assert(jsonMap['assetName'] is String); + assert(jsonMap['bitmapScaling'] is String); + assert(jsonMap['imagePixelRatio'] is double); + assert(!jsonMap.containsKey('width') || jsonMap['width'] is double); + assert(!jsonMap.containsKey('height') || jsonMap['height'] is double); + case BytesMapBitmap.type: + assert(jsonList.length == 2); + assert(jsonList[1] != null && jsonList[1] is Map); + final Map jsonMap = + jsonList[1] as Map; + assert(jsonMap.containsKey('byteData')); + assert(jsonMap.containsKey('bitmapScaling')); + assert(jsonMap.containsKey('imagePixelRatio')); + assert(jsonMap['byteData'] is Uint8List); + assert(jsonMap['bitmapScaling'] is String); + assert(jsonMap['imagePixelRatio'] is double); + assert(!jsonMap.containsKey('width') || jsonMap['width'] is double); + assert(!jsonMap.containsKey('height') || jsonMap['height'] is double); default: break; } } static const String _defaultMarker = 'defaultMarker'; + static const String _fromAsset = 'fromAsset'; static const String _fromAssetImage = 'fromAssetImage'; static const String _fromBytes = 'fromBytes'; @@ -68,6 +125,8 @@ class BitmapDescriptor { _fromAsset, _fromAssetImage, _fromBytes, + AssetMapBitmap.type, + BytesMapBitmap.type, }; /// Convenience hue value representing red. @@ -115,10 +174,12 @@ class BitmapDescriptor { /// Creates a [BitmapDescriptor] from an asset image. /// /// Asset images in flutter are stored per: - /// https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets + /// https://flutter.dev/to/resolution-aware-images /// This method takes into consideration various asset resolutions /// and scales the images to the right resolution depending on the dpi. - /// Set `mipmaps` to false to load the exact dpi version of the image, `mipmap` is true by default. + /// Set `mipmaps` to false to load the exact dpi version of the image, + /// `mipmap` is true by default. + @Deprecated('Use BitmapDescriptor.asset method instead.') static Future fromAssetImage( ImageConfiguration configuration, String assetName, { @@ -157,6 +218,7 @@ class BitmapDescriptor { /// bitmap, regardless of the actual resolution of the encoded PNG. /// This helps the browser to render High-DPI images at the correct size. /// `size` is not required (and ignored, if passed) in other platforms. + @Deprecated('Use BitmapDescriptor.bytes method instead.') static BitmapDescriptor fromBytes(Uint8List byteData, {Size? size}) { assert(byteData.isNotEmpty, 'Cannot create BitmapDescriptor with empty byteData'); @@ -171,8 +233,610 @@ class BitmapDescriptor { ]); } + /// Creates a [BitmapDescriptor] from an asset using [AssetMapBitmap]. + /// + /// This method wraps [AssetMapBitmap.create] for ease of use within the + /// context of creating [BitmapDescriptor] instances. It dynamically resolves + /// the correct asset version based on the device's pixel ratio, ensuring + /// optimal resolution without manual configuration. + /// + /// [configuration] provides the image configuration for the asset. + /// [assetName] is the name of the asset to load. + /// [bundle] and [package] specify the asset's location if outside of the + /// default. + /// [width] and [height] can optionally control the dimensions of the rendered + /// image. + /// [imagePixelRatio] controls the scale of the image relative to the device's + /// pixel ratio. It defaults resolved asset image pixel ratio. The value is + /// ignored if [width] or [height] is provided. + /// + /// See [AssetMapBitmap.create] for more information on the parameters. + /// + /// Returns a Future that completes with a new [AssetMapBitmap] instance. + static Future asset( + ImageConfiguration configuration, + String assetName, { + AssetBundle? bundle, + String? package, + double? width, + double? height, + double? imagePixelRatio, + MapBitmapScaling bitmapScaling = MapBitmapScaling.auto, + }) async { + return AssetMapBitmap.create( + configuration, + assetName, + bundle: bundle, + package: package, + width: width, + height: height, + imagePixelRatio: imagePixelRatio, + bitmapScaling: bitmapScaling, + ); + } + + /// Creates a [BitmapDescriptor] from byte data using [BytesMapBitmap]. + /// + /// This method wraps [BytesMapBitmap] constructor for ease of use within the + /// context of creating [BitmapDescriptor] instances. + /// + /// [byteData] is the PNG-encoded image data. + /// [imagePixelRatio] controls the scale of the image relative to the device's + /// pixel ratio. It defaults to the natural resolution if not specified. + /// The value is ignored if [width] or [height] is provided. + /// [width] and [height] can optionally control the dimensions of the rendered + /// image. + /// + /// See [BytesMapBitmap] for more information on the parameters. + /// + /// Returns a new [BytesMapBitmap] instance. + static BytesMapBitmap bytes( + Uint8List byteData, { + double? imagePixelRatio, + double? width, + double? height, + MapBitmapScaling bitmapScaling = MapBitmapScaling.auto, + }) { + return BytesMapBitmap( + byteData, + imagePixelRatio: imagePixelRatio, + width: width, + height: height, + bitmapScaling: bitmapScaling, + ); + } + final Object _json; /// Convert the object to a Json format. Object toJson() => _json; } + +/// Represents a [BitmapDescriptor] base class for map bitmaps. +/// +/// See [AssetMapBitmap] and [BytesMapBitmap] for concrete implementations. +/// +/// The [imagePixelRatio] should be set to the correct pixel ratio of bitmap +/// image. If the [width] or [height] is provided, the [imagePixelRatio] +/// value is ignored. +/// +/// [bitmapScaling] controls the scaling behavior: +/// - [MapBitmapScaling.auto] automatically upscales and downscales the image +/// to match the device's pixel ratio or the specified dimensions, +/// maintaining consistency across devices. +/// - [MapBitmapScaling.none] disables automatic scaling, which is +/// useful when performance is a concern or if the asset is already scaled +/// appropriately. +/// +/// Optionally, [width] and [height] can be specified to control the dimensions +/// of the rendered image: +/// - If both [width] and [height] are non-null, the image will have the +/// specified dimensions, which might distort the original aspect ratio, +/// similar to [BoxFit.fill]. +/// - If only one of [width] and [height] is non-null, then the output image +/// will be scaled to the associated width or height, and the other dimension +/// will take whatever value is needed to maintain the image's original aspect +/// ratio. These cases are similar to [BoxFit.fitWidth] and +/// [BoxFit.fitHeight], respectively. +abstract class MapBitmap extends BitmapDescriptor { + MapBitmap._({ + required this.bitmapScaling, + required this.imagePixelRatio, + this.width, + this.height, + }) : super._(const []); + + /// The scaling method of the bitmap. + final MapBitmapScaling bitmapScaling; + + /// The pixel ratio of the bitmap. + /// + /// If the [width] or [height] is provided, the [imagePixelRatio] + /// value is ignored. + final double imagePixelRatio; + + /// The target width of the bitmap in logical pixels. + /// + /// - If [width] is provided and [height] is null, the image will be scaled to + /// the associated width, and the height will take whatever value is needed + /// to maintain the image's original aspect ratio. This is similar to + /// [BoxFit.fitWidth]. + /// - If both [width] and [height] are non-null, the image will have the + /// specified dimensions, which might distort the original aspect ratio, + /// similar to [BoxFit.fill]. + /// - If neither [width] nor [height] is provided, the image will be rendered + /// using the [imagePixelRatio] value. + final double? width; + + /// The target height of the bitmap in logical pixels. + /// + /// - If [height] is provided and [width] is null, the image will be scaled to + /// the associated height, and the width will take whatever value is needed + /// to maintain the image's original aspect ratio. This is similar to + /// [BoxFit.fitHeight]. + /// - If both [width] and [height] are non-null, the image will have the + /// specified dimensions, which might distort the original aspect ratio, + /// similar to [BoxFit.fill]. + /// - If neither [width] nor [height] is provided, the image will be rendered + /// using the [imagePixelRatio] value. + final double? height; +} + +/// Represents a [BitmapDescriptor] that is created from an asset image. +/// +/// This class extends [BitmapDescriptor] to support loading images from assets +/// and mipmaps. It allows resolving the assets that are optimized +/// for the device's screen resolution and pixel density. +/// +/// Use [AssetMapBitmap.create] as the default method for generating +/// instances of this class. It dynamically resolves the correct asset version +/// based on the device's pixel ratio, ensuring optimal resolution without +/// manual configuration. +/// See https://flutter.dev/to/resolution-aware-images +/// for more information on resolution-aware assets. +/// +/// Note that it's important to either provide high-resolution +/// assets to gain sharp images on high-density screens or set the +/// [imagePixelRatio], [width] or [height] values to control the render size. +/// +/// Following example demonstrates how to create an [AssetMapBitmap] +/// using asset resolving: +/// +/// ```dart +/// Future _getAssetMapBitmap(BuildContext context) async { +/// final ImageConfiguration imageConfiguration = createLocalImageConfiguration( +/// context, +// ); +/// AssetMapBitmap assetMapBitmap = await AssetMapBitmap.create( +/// imageConfiguration, +/// 'assets/images/map_icon.png', +/// ); +/// return assetMapBitmap; +/// } +/// ``` +/// +/// Optionally, [width] and [height] can be specified to control the dimensions +/// of the rendered image: +/// - If both [width] and [height] are non-null, the image will have the +/// specified dimensions, which might distort the original aspect ratio, +/// similar to [BoxFit.fill]. +/// - If only one of [width] and [height] is non-null, then the output image +/// will be scaled to the associated width or height, and the other dimension +/// will take whatever value is needed to maintain the image's original aspect +/// ratio. These cases are similar to [BoxFit.fitWidth] and +/// [BoxFit.fitHeight], respectively. +/// +/// ```dart +/// Future _getAssetMapBitmap(BuildContext context) async { +/// final ImageConfiguration imageConfiguration = createLocalImageConfiguration( +/// context, +/// ); +/// // Render the image at exact size of 64x64 logical pixels. +/// AssetMapBitmap assetMapBitmap = await AssetMapBitmap.create( +/// imageConfiguration, +/// 'assets/images/map_icon.png', +/// width: 64, // Desired width in logical pixels. +/// height: 64, // Desired height in logical pixels. +/// ); +/// return assetMapBitmap; +/// } +/// ``` +/// +/// The following example demonstrates how to create an [AssetMapBitmap] from +/// an asset image without automatic mipmap resolving: +/// +/// ```dart +/// AssetMapBitmap assetMapBitmap = AssetMapBitmap( +/// 'assets/images/map_icon.png', +/// ); +/// ``` +/// +/// To render the bitmap as sharply as possible, set the [imagePixelRatio] to +/// the device's pixel ratio. This renders the asset at a pixel-to-pixel ratio +/// on the screen, but may result in different logical marker sizes across +/// devices with varying pixel densities. +/// +///```dart +/// AssetMapBitmap assetMapBitmap = AssetMapBitmap( +/// 'assets/images/map_icon.png', +/// imagePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context), +/// ); +/// ``` +class AssetMapBitmap extends MapBitmap { + /// Creates a [AssetMapBitmap] from an asset image. + /// + /// To create an instance of [AssetMapBitmap] from mipmapped assets, use the + /// asynchronous [AssetMapBitmap.create] method instead of this constructor. + /// + /// The [imagePixelRatio] parameter allows to give correct pixel ratio of the + /// asset image. If the [imagePixelRatio] is not provided, value is defaulted + /// to the natural resolution of 1.0. To render the asset as sharp as + /// possible, set the [imagePixelRatio] to the devices pixel ratio. + /// + /// [bitmapScaling] controls the scaling behavior: + /// - [MapBitmapScaling.auto] automatically upscales and downscales the image + /// to match the device's pixel ratio or the specified dimensions, + /// maintaining consistency across devices. + /// - [MapBitmapScaling.none] disables automatic scaling, which is + /// useful when performance is a concern or if the asset is already scaled + /// appropriately. + /// + /// Optionally, [width] and [height] can be specified to control the dimensions + /// of the rendered image: + /// - If both [width] and [height] are non-null, the image will have the + /// specified dimensions, which might distort the original aspect ratio, + /// similar to [BoxFit.fill]. + /// - If only one of [width] and [height] is non-null, then the output image + /// will be scaled to the associated width or height, and the other dimension + /// will take whatever value is needed to maintain the image's original aspect + /// ratio. These cases are similar to [BoxFit.fitWidth] and + /// [BoxFit.fitHeight], respectively. + /// + /// If [width] or [height] is provided, [imagePixelRatio] value is ignored. + /// + /// The following example demonstrates how to create an [AssetMapBitmap] from + /// an asset image without automatic asset resolving: + /// + /// ```dart + /// AssetMapBitmap mapBitmap = AssetMapBitmap( + /// 'assets/images/map_icon.png', + /// bitmapScaling: MapBitmapScaling.auto, + /// width: 40, // Desired width in logical pixels. + /// ); + /// ``` + /// + /// To render the bitmap as sharply as possible, set the [imagePixelRatio] to + /// the device's pixel ratio. This renders the asset at a pixel-to-pixel ratio + /// on the screen, but may result in different logical marker sizes across + /// devices with varying pixel densities. + /// + ///```dart + /// AssetMapBitmap assetMapBitmap = AssetMapBitmap( + /// 'assets/images/map_icon.png', + /// imagePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context), + /// ); + /// ``` + AssetMapBitmap( + String assetName, { + MapBitmapScaling bitmapScaling = MapBitmapScaling.auto, + double? imagePixelRatio, + double? width, + double? height, + }) : this._( + assetName: assetName, + bitmapScaling: bitmapScaling, + imagePixelRatio: imagePixelRatio ?? _naturalPixelRatio, + width: width, + height: height, + ); + + /// Internal constructor for creating a [AssetMapBitmap]. + AssetMapBitmap._({ + required this.assetName, + required super.imagePixelRatio, + required super.bitmapScaling, + super.width, + super.height, + }) : assert(assetName.isNotEmpty, 'The asset name must not be empty.'), + assert(imagePixelRatio > 0.0, + 'The imagePixelRatio must be greater than 0.'), + assert(bitmapScaling != MapBitmapScaling.none || width == null, + 'If bitmapScaling is set to MapBitmapScaling.none, width parameter cannot be used.'), + assert(bitmapScaling != MapBitmapScaling.none || height == null, + 'If bitmapScaling is set to MapBitmapScaling.none, height parameter cannot be used.'), + super._(); + + /// The type of the [BitmapDescriptor] object, used for the + /// JSON serialization. + static const String type = 'asset'; + + /// The name of the asset. + final String assetName; + + /// Creates a [AssetMapBitmap] from an asset image with asset resolving and + /// mipmapping enabled. + /// + /// This method dynamically resolves the correct asset version based on the + /// device's pixel ratio, ensuring optimal resolution without manual + /// configuration. It is the preferred method for creating instances of + /// [AssetMapBitmap] due to its automatic asset resolution capabilities. + /// + /// [assetName] is the name of the asset. The asset is resolved in the context + /// of the specified [bundle] and [package]. + /// + /// Optionally, [width] and [height] can be specified to control the + /// dimensions of the rendered image: + /// - If both [width] and [height] are non-null, the image will have the + /// specified dimensions, which might distort the original aspect ratio, + /// similar to [BoxFit.fill]. + /// - If only one of [width] and [height] is non-null, then the output image + /// will be scaled to the associated width or height, and the other + /// dimension will take whatever value is needed to maintain the image's + /// original aspect ratio. These cases are similar to [BoxFit.fitWidth] and + /// [BoxFit.fitHeight], respectively. + /// + /// [bitmapScaling] controls the scaling behavior: + /// - [MapBitmapScaling.auto] automatically upscales and downscales the image + /// to match the device's pixel ratio or the specified dimensions, + /// maintaining consistency across devices. + /// - [MapBitmapScaling.none] disables automatic scaling, which is + /// useful when performance is a concern or if the asset is already scaled + /// appropriately. + /// + /// Asset mipmap is resolved using the devices pixel ratio from the + /// [ImageConfiguration.devicePixelRatio] parameter. To initialize the + /// [ImageConfiguration] with the devices pixel ratio, use the + /// [createLocalImageConfiguration] method. + /// + /// [imagePixelRatio] can be provided to override the resolved asset's pixel + /// ratio. Specifying [imagePixelRatio] can be useful in scenarios where + /// custom scaling is needed. [imagePixelRatio] is ignored if [width] or + /// [height] is provided. + /// + /// Returns a Future that completes with an [AssetMapBitmap] instance. + /// + /// Following example demonstrates how to create an [AssetMapBitmap] + /// using asset resolving: + /// + /// ```dart + /// Future _getAssetMapBitmap(BuildContext context) async { + /// final ImageConfiguration imageConfiguration = createLocalImageConfiguration( + /// context, + // ); + /// AssetMapBitmap assetMapBitmap = await AssetMapBitmap.create( + /// imageConfiguration, + /// 'assets/images/map_icon.png', + /// ); + /// return assetMapBitmap; + /// } + /// ``` + /// + /// Optionally, [width] and [height] can be specified to control the + /// asset's dimensions: + /// + /// ```dart + /// Future _getAssetMapBitmap(BuildContext context) async { + /// final ImageConfiguration imageConfiguration = createLocalImageConfiguration( + /// context, + /// ); + /// AssetMapBitmap assetMapBitmap = await AssetMapBitmap.create( + /// imageConfiguration, + /// 'assets/images/map_icon.png', + /// width: 64, // Desired width in logical pixels. + /// height: 64, // Desired height in logical pixels. + /// ); + /// return assetMapBitmap; + /// } + /// ``` + static Future create( + ImageConfiguration configuration, + String assetName, { + AssetBundle? bundle, + String? package, + double? width, + double? height, + double? imagePixelRatio, + MapBitmapScaling bitmapScaling = MapBitmapScaling.auto, + }) async { + assert(assetName.isNotEmpty, 'The asset name must not be empty.'); + final AssetImage assetImage = + AssetImage(assetName, package: package, bundle: bundle); + final AssetBundleImageKey assetBundleImageKey = + await assetImage.obtainKey(configuration); + + return AssetMapBitmap._( + assetName: assetBundleImageKey.name, + imagePixelRatio: imagePixelRatio ?? assetBundleImageKey.scale, + bitmapScaling: bitmapScaling, + width: width ?? configuration.size?.width, + height: height ?? configuration.size?.height); + } + + @override + Object toJson() => [ + type, + { + 'assetName': assetName, + 'bitmapScaling': bitmapScaling.name, + 'imagePixelRatio': imagePixelRatio, + if (width != null) 'width': width, + if (height != null) 'height': height, + } + ]; +} + +/// Represents a [BitmapDescriptor] that is created from an array of bytes +/// encoded as `PNG` in [Uint8List]. +/// +/// The [byteData] represents the image in a `PNG` format, which will be +/// decoded and rendered by the platform. The optional [width], [height] or +/// [imagePixelRatio] parameters are used to correctly scale the image for +/// display, taking into account the devices pixel ratio. +/// +/// [bitmapScaling] controls the scaling behavior: +/// - [MapBitmapScaling.auto] automatically upscales and downscales the image +/// to match the device's pixel ratio or the specified dimensions, +/// maintaining consistency across devices. +/// - [MapBitmapScaling.none] disables automatic scaling, which is +/// useful when performance is a concern or if the asset is already scaled +/// appropriately. +/// +/// The [imagePixelRatio] parameter allows to give correct pixel ratio of the +/// image. If the [imagePixelRatio] is not provided, value is defaulted +/// to the natural resolution of 1.0. To render the asset as sharp as possible, +/// set the [imagePixelRatio] to the devices pixel ratio. [imagePixelRatio] is +/// ignored if [width] or [height] is provided. +/// +/// Optionally, [width] and [height] can be specified to control the +/// dimensions of the rendered image: +/// - If both [width] and [height] are non-null, the image will have the +/// specified dimensions, which might distort the original aspect ratio, +/// similar to [BoxFit.fill]. +/// - If only one of [width] and [height] is non-null, then the output image +/// will be scaled to the associated width or height, and the other +/// dimension will take whatever value is needed to maintain the image's +/// original aspect ratio. These cases are similar to [BoxFit.fitWidth] and +/// [BoxFit.fitHeight], respectively. +/// +/// The following example demonstrates how to create an [BytesMapBitmap] from +/// a list of bytes in [Uint8List] format: +/// +/// ```dart +/// Uint8List byteData = imageBuffer.asUint8List() +/// double imagePixelRatio = 2.0; // Pixel density of the image. +/// BytesMapBitmap bytesMapBitmap = BytesMapBitmap( +/// byteData, +/// imagePixelRatio: imagePixelRatio, +/// ); +/// ``` +/// +/// Optionally, [width] and [height] can be specified to control the +/// asset's dimensions: +/// +/// ```dart +/// Uint8List byteData = imageBuffer.asUint8List() +/// BytesMapBitmap bytesMapBitmap = BytesMapBitmap( +/// byteData, +/// width: 64, // Desired width in logical pixels. +/// ); +/// ``` +/// +/// To render the bitmap as sharply as possible, set the [imagePixelRatio] to +/// the device's pixel ratio. This renders the asset at a pixel-to-pixel ratio +/// on the screen, but may result in different logical marker sizes across +/// devices with varying pixel densities. +/// +///```dart +/// Uint8List byteData = imageBuffer.asUint8List() +/// BytesMapBitmap bytesMapBitmap = BytesMapBitmap( +/// byteData, +/// imagePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context), +/// ); +/// ``` +class BytesMapBitmap extends MapBitmap { + /// Constructs a [BytesMapBitmap] that is created from an array of bytes that + /// must be encoded as `PNG` in [Uint8List]. + /// + /// The [byteData] represents the image in a `PNG` format, which will be + /// decoded and rendered by the platform. The optional [width], [height] or + /// [imagePixelRatio] parameters are used to correctly scale the image for + /// display, taking into account the devices pixel ratio. + /// + /// [bitmapScaling] controls the scaling behavior: + /// - [MapBitmapScaling.auto] automatically upscales and downscales the image + /// to match the device's pixel ratio or the specified dimensions, + /// maintaining consistency across devices. + /// - [MapBitmapScaling.none] disables automatic scaling, which is + /// useful when performance is a concern or if the asset is already scaled + /// appropriately. + /// + /// The [imagePixelRatio] parameter allows to give correct pixel ratio of the + /// image. If the [imagePixelRatio] is not provided, value is defaulted + /// to the natural resolution of 1.0. To render the asset as sharp as possible, + /// set the [imagePixelRatio] to the devices pixel ratio. [imagePixelRatio] is + /// ignored if [width] or [height] is provided. + /// + /// Optionally, [width] and [height] can be specified to control the + /// dimensions of the rendered image: + /// - If both [width] and [height] are non-null, the image will have the + /// specified dimensions, which might distort the original aspect ratio, + /// similar to [BoxFit.fill]. + /// - If only one of [width] and [height] is non-null, then the output image + /// will be scaled to the associated width or height, and the other + /// dimension will take whatever value is needed to maintain the image's + /// original aspect ratio. These cases are similar to [BoxFit.fitWidth] and + /// [BoxFit.fitHeight], respectively. + /// + /// Throws an [AssertionError] if [byteData] is empty or if incompatible + /// scaling options are provided. + /// + /// The following example demonstrates how to create an [BytesMapBitmap] from + /// a list of bytes in [Uint8List] format: + /// + /// ```dart + /// Uint8List byteData = await _loadImageData('path/to/image.png'); + /// double imagePixelRatio = 2.0; // Pixel density of the image. + /// BytesMapBitmap bytesMapBitmap = BytesMapBitmap( + /// byteData, + /// imagePixelRatio: imagePixelRatio, + /// ); + /// ``` + /// + /// Optionally, [width] and [height] can be specified to control the + /// asset's dimensions: + /// + /// ```dart + /// Uint8List byteData = imageBuffer.asUint8List() + /// BytesMapBitmap bytesMapBitmap = BytesMapBitmap( + /// byteData, + /// width: 64, // Desired width in logical pixels. + /// ); + /// ``` + /// + /// To render the bitmap as sharply as possible, set the [imagePixelRatio] to + /// the device's pixel ratio. This renders the asset at a pixel-to-pixel ratio + /// on the screen, but may result in different logical marker sizes across + /// devices with varying pixel densities. + /// + ///```dart + /// Uint8List byteData = imageBuffer.asUint8List() + /// BytesMapBitmap bytesMapBitmap = BytesMapBitmap( + /// byteData, + /// imagePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context), + /// ); + /// ``` + BytesMapBitmap( + this.byteData, { + super.bitmapScaling = MapBitmapScaling.auto, + super.width, + super.height, + double? imagePixelRatio, + }) : assert(byteData.isNotEmpty, + 'Cannot create BitmapDescriptor with empty byteData.'), + assert( + bitmapScaling != MapBitmapScaling.none || imagePixelRatio == null, + 'If bitmapScaling is set to MapBitmapScaling.none, imagePixelRatio parameter cannot be used.'), + assert(bitmapScaling != MapBitmapScaling.none || width == null, + 'If bitmapScaling is set to MapBitmapScaling.none, width parameter cannot be used.'), + assert(bitmapScaling != MapBitmapScaling.none || height == null, + 'If bitmapScaling is set to MapBitmapScaling.none, height parameter cannot be used.'), + super._(imagePixelRatio: imagePixelRatio ?? _naturalPixelRatio); + + /// The type of the MapBitmap object, used for the JSON serialization. + static const String type = 'bytes'; + + /// The bytes of the bitmap. + final Uint8List byteData; + + @override + Object toJson() => [ + type, + { + 'byteData': byteData, + 'bitmapScaling': bitmapScaling.name, + 'imagePixelRatio': imagePixelRatio, + if (width != null) 'width': width, + if (height != null) 'height': height, + } + ]; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart index 8c4d7b26797..0921d2d4ec5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart @@ -192,6 +192,12 @@ class Marker implements MapsObject { final bool flat; /// A description of the bitmap used to draw the marker icon. + /// + /// To create marker icon from assets, use [AssetMapBitmap], + /// [AssetMapBitmap.create] or [BitmapDescriptor.asset]. + /// + /// To create marker icon from raw PNG data use [BytesMapBitmap] + /// or [BitmapDescriptor.bytes]. final BitmapDescriptor icon; /// A Google Maps InfoWindow. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index ec9271aab71..e9d672efb1c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,11 +4,11 @@ repository: https://github.com/flutter/packages/tree/main/packages/google_maps_f issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.6.0 +version: 2.8.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: collection: ^1.15.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart index 0d10abd1852..15a6833e935 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart @@ -80,6 +80,7 @@ void main() { expect(BitmapDescriptor.fromJson(['defaultMarker']), isA()); }); + test('wrong type', () { expect(() { BitmapDescriptor.fromJson(['bogusType']); @@ -91,15 +92,18 @@ void main() { expect(BitmapDescriptor.fromJson(['defaultMarker']), isA()); }); + test('hue is number', () { expect(BitmapDescriptor.fromJson(['defaultMarker', 158]), isA()); }); + test('hue is not number', () { expect(() { BitmapDescriptor.fromJson(['defaultMarker', 'nope']); }, throwsAssertionError); }); + test('hue is out of range', () { expect(() { BitmapDescriptor.fromJson(['defaultMarker', -1]); @@ -118,6 +122,7 @@ void main() { ]), isA()); }); + test('without bytes', () { expect(() { BitmapDescriptor.fromJson(['fromBytes', null]); @@ -134,6 +139,7 @@ void main() { ['fromAsset', 'some/path.png']), isA()); }); + test('name cannot be null or empty', () { expect(() { BitmapDescriptor.fromJson(['fromAsset', null]); @@ -142,12 +148,14 @@ void main() { BitmapDescriptor.fromJson(['fromAsset', '']); }, throwsAssertionError); }); + test('package is passed', () { expect( BitmapDescriptor.fromJson( ['fromAsset', 'some/path.png', 'some_package']), isA()); }); + test('package cannot be null or empty', () { expect(() { BitmapDescriptor.fromJson( @@ -196,6 +204,7 @@ void main() { BitmapDescriptor.fromJson(['fromAssetImage', '', 1.0]); }, throwsAssertionError); }); + test('dpi must be number', () { expect(() { BitmapDescriptor.fromJson( @@ -206,6 +215,7 @@ void main() { ['fromAssetImage', 'some/path.png', 'one']); }, throwsAssertionError); }); + test('with optional [width, height] List', () { expect( BitmapDescriptor.fromJson([ @@ -216,6 +226,7 @@ void main() { ]), isA()); }); + test( 'optional [width, height] List cannot be null or not contain 2 elements', () { @@ -237,6 +248,366 @@ void main() { }, throwsAssertionError); }); }); + + group('bytes', () { + test('with bytes', () { + expect( + BitmapDescriptor.fromJson([ + BytesMapBitmap.type, + { + 'byteData': Uint8List.fromList([1, 2, 3]), + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'width': 1.0, + 'height': 1.0, + } + ]), + isA()); + }); + + test('without bytes', () { + expect(() { + BitmapDescriptor.fromJson( + [BytesMapBitmap.type, null, 'auto', 3.0]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson( + [BytesMapBitmap.type, [], 'auto', 3.0]); + }, throwsAssertionError); + }); + }); + + group('asset', () { + test('name and dpi passed', () { + expect( + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + } + ]), + isA()); + }); + + test('name cannot be null or empty', () { + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + null, + 'auto', + 1.0, + ]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + '', + 'auto', + 1.0, + ]); + }, throwsAssertionError); + }); + + test('dpi must be number', () { + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 'string', + } + ]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': null, + } + ]); + }, throwsAssertionError); + }); + + test('with optional [width, height]', () { + expect( + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'width': 1.0, + 'height': 1.0, + } + ]), + isA()); + }); + + test('optional width and height parameters must be in proper format', + () { + expect(() { + BitmapDescriptor.fromJson([ + 'fromAssetImage', + 'some/path.png', + 'auto', + 1.0, + null + ]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson([ + 'fromAssetImage', + 'some/path.png', + 'auto', + 1.0, + [] + ]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + 'some/path.png', + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': null, + 'width': null, + 'height': 1.0, + } + ]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + 'some/path.png', + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': null, + 'width': 1.0, + 'height': null, + } + ]); + }, throwsAssertionError); + expect(() { + BitmapDescriptor.fromJson([ + AssetMapBitmap.type, + 'some/path.png', + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': null, + 'width': '1.0', + } + ]); + }, throwsAssertionError); + }); + }); + }); + }); + + group('AssetMapBitmap', () { + test('construct', () async { + final BitmapDescriptor descriptor = AssetMapBitmap( + 'red_square.png', + ); + expect(descriptor, isA()); + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + } + ])); + }); + + test('construct with imagePixelRatio', () async { + final BitmapDescriptor descriptor = + AssetMapBitmap('red_square.png', imagePixelRatio: 1.2345); + + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.2345, + } + ])); + }); + + test('construct with width', () async { + const double width = 100; + final BitmapDescriptor descriptor = + AssetMapBitmap('red_square.png', width: width); + + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'width': width, + } + ])); + }); + + test('create', () async { + final BitmapDescriptor descriptor = await AssetMapBitmap.create( + ImageConfiguration.empty, 'red_square.png'); + expect(descriptor, isA()); + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0 + } + ])); + }, + // TODO(stuartmorgan): Investigate timeout on web. + skip: kIsWeb); + + test('create with size', () async { + const Size size = Size(100, 200); + const ImageConfiguration imageConfiguration = + ImageConfiguration(size: size); + final BitmapDescriptor descriptor = + await AssetMapBitmap.create(imageConfiguration, 'red_square.png'); + + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'width': 100.0, + 'height': 200.0 + } + ])); + }); + test('create with width', () async { + const ImageConfiguration imageConfiguration = ImageConfiguration.empty; + final BitmapDescriptor descriptor = await AssetMapBitmap.create( + imageConfiguration, 'red_square.png', + width: 100); + + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'width': 100.0, + } + ])); + }); + test('create with height', () async { + const ImageConfiguration imageConfiguration = ImageConfiguration.empty; + final BitmapDescriptor descriptor = await AssetMapBitmap.create( + imageConfiguration, 'red_square.png', + height: 200); + + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + AssetMapBitmap.type, + { + 'assetName': 'red_square.png', + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'height': 200.0 + } + ])); + }); + }, + // TODO(stuartmorgan): Investigate timeout on web. + skip: kIsWeb); + + group('BytesMapBitmap', () { + test('construct with empty byte array, throws assertion error', () { + expect(() { + BytesMapBitmap(Uint8List.fromList([])); + }, throwsAssertionError); + }); + + test('construct', () { + final BitmapDescriptor descriptor = BytesMapBitmap( + Uint8List.fromList([1, 2, 3]), + ); + expect(descriptor, isA()); + expect( + descriptor.toJson(), + equals([ + BytesMapBitmap.type, + { + 'byteData': [1, 2, 3], + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + } + ])); + }); + + test('construct with width', () { + const double width = 100; + final BitmapDescriptor descriptor = BytesMapBitmap( + Uint8List.fromList([1, 2, 3]), + width: width, + ); + + expect( + descriptor.toJson(), + equals([ + BytesMapBitmap.type, + { + 'byteData': [1, 2, 3], + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.0, + 'width': 100.0 + } + ])); + }); + + test('construct with imagePixelRatio', () { + final BitmapDescriptor descriptor = BytesMapBitmap( + Uint8List.fromList([1, 2, 3]), + imagePixelRatio: 1.2345, + ); + + expect( + descriptor.toJson(), + equals([ + BytesMapBitmap.type, + { + 'byteData': [1, 2, 3], + 'bitmapScaling': MapBitmapScaling.auto.name, + 'imagePixelRatio': 1.2345 + } + ])); }); }); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_web/AUTHORS index b5b0e84d473..906ab219bee 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/AUTHORS +++ b/packages/google_maps_flutter/google_maps_flutter_web/AUTHORS @@ -65,3 +65,4 @@ Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> Justin Baumann +Joonas Kerttula diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 08d4608228c..a519cfc7ada 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.5.8 + +* Adds support for BitmapDescriptor classes `AssetMapBitmap` and `BytesMapBitmap`. + +## 0.5.7 + +* Adds support for marker clustering. + ## 0.5.6+2 * Uses `TrustedTypes` from `web: ^0.5.1`. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/README.md b/packages/google_maps_flutter/google_maps_flutter_web/README.md index b52da95119c..c87e1082025 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/README.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/README.md @@ -6,7 +6,7 @@ Powered by [a14n](https://github.com/a14n)'s [google_maps](https://pub.dev/packa ## Usage -This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +This package is [endorsed](https://flutter.dev/to/endorsed-federated-plugin), which means you can simply use `google_maps_flutter` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. @@ -50,6 +50,19 @@ To request multiple libraries, separate them with commas: Now you should be able to use the Google Maps plugin normally. +## Marker clustering + +If you need marker clustering support, modify the tag to load the [js-markerclusterer](https://github.com/googlemaps/js-markerclusterer#install) library. Ensure you are using the currently supported version `2.5.3`, like so: + +```html + + + + + + +``` + ## Limitations of the web version The following map options are not available in web, because the map doesn't rotate there: diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/README.md b/packages/google_maps_flutter/google_maps_flutter_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/README.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/assets/red_square.png b/packages/google_maps_flutter/google_maps_flutter_web/example/assets/red_square.png new file mode 100644 index 00000000000..650a2dee711 Binary files /dev/null and b/packages/google_maps_flutter/google_maps_flutter_web/example/assets/red_square.png differ diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/build.excerpt.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/build.excerpt.yaml new file mode 100644 index 00000000000..e317efa11cb --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/build.excerpt.yaml @@ -0,0 +1,15 @@ +targets: + $default: + sources: + include: + - lib/** + # Some default includes that aren't really used here but will prevent + # false-negative warnings: + - $package$ + - lib/$lib$ + exclude: + - '**/.*/**' + - '**/build/**' + builders: + code_excerpter|code_excerpter: + enabled: true diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart index aa7e0ae4fe4..d7c9ae288f4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart @@ -181,14 +181,12 @@ void main() { (WidgetTester tester) async { controller.dispose(); - expect(() { - controller.updateMarkers( - MarkerUpdates.from( + await expectLater( + controller.updateMarkers(MarkerUpdates.from( const {}, const {}, - ), - ); - }, throwsAssertionError); + )), + throwsAssertionError); expect(() { controller.showInfoWindow(const MarkerId('any')); @@ -674,7 +672,7 @@ void main() { const Marker(markerId: MarkerId('to-be-added')), }; - controller.updateMarkers(MarkerUpdates.from(previous, current)); + await controller.updateMarkers(MarkerUpdates.from(previous, current)); verify(mock.removeMarkers({ const MarkerId('to-be-removed'), diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart index c85599934ba..aef35cc4046 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart @@ -3,6 +3,8 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; + import 'package:google_maps/google_maps.dart' as _i2; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' as _i4; @@ -42,7 +44,6 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { returnValue: <_i4.CircleId, _i3.CircleController>{}, returnValueForMissingStub: <_i4.CircleId, _i3.CircleController>{}, ) as Map<_i4.CircleId, _i3.CircleController>); - @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), @@ -55,7 +56,6 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { Invocation.getter(#googleMap), ), ) as _i2.GMap); - @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( @@ -64,14 +64,12 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { ), returnValueForMissingStub: null, ); - @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); - @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( @@ -80,7 +78,6 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { ), returnValueForMissingStub: null, ); - @override void addCircles(Set<_i4.Circle>? circlesToAdd) => super.noSuchMethod( Invocation.method( @@ -89,7 +86,6 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { ), returnValueForMissingStub: null, ); - @override void changeCircles(Set<_i4.Circle>? circlesToChange) => super.noSuchMethod( Invocation.method( @@ -98,7 +94,6 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { ), returnValueForMissingStub: null, ); - @override void removeCircles(Set<_i4.CircleId>? circleIdsToRemove) => super.noSuchMethod( @@ -108,7 +103,6 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { ), returnValueForMissingStub: null, ); - @override void bindToMap( int? mapId, @@ -137,7 +131,6 @@ class MockPolygonsController extends _i1.Mock returnValue: <_i4.PolygonId, _i3.PolygonController>{}, returnValueForMissingStub: <_i4.PolygonId, _i3.PolygonController>{}, ) as Map<_i4.PolygonId, _i3.PolygonController>); - @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), @@ -150,7 +143,6 @@ class MockPolygonsController extends _i1.Mock Invocation.getter(#googleMap), ), ) as _i2.GMap); - @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( @@ -159,14 +151,12 @@ class MockPolygonsController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); - @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( @@ -175,7 +165,6 @@ class MockPolygonsController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void addPolygons(Set<_i4.Polygon>? polygonsToAdd) => super.noSuchMethod( Invocation.method( @@ -184,7 +173,6 @@ class MockPolygonsController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void changePolygons(Set<_i4.Polygon>? polygonsToChange) => super.noSuchMethod( Invocation.method( @@ -193,7 +181,6 @@ class MockPolygonsController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removePolygons(Set<_i4.PolygonId>? polygonIdsToRemove) => super.noSuchMethod( @@ -203,7 +190,6 @@ class MockPolygonsController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void bindToMap( int? mapId, @@ -232,7 +218,6 @@ class MockPolylinesController extends _i1.Mock returnValue: <_i4.PolylineId, _i3.PolylineController>{}, returnValueForMissingStub: <_i4.PolylineId, _i3.PolylineController>{}, ) as Map<_i4.PolylineId, _i3.PolylineController>); - @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), @@ -245,7 +230,6 @@ class MockPolylinesController extends _i1.Mock Invocation.getter(#googleMap), ), ) as _i2.GMap); - @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( @@ -254,14 +238,12 @@ class MockPolylinesController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); - @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( @@ -270,7 +252,6 @@ class MockPolylinesController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void addPolylines(Set<_i4.Polyline>? polylinesToAdd) => super.noSuchMethod( Invocation.method( @@ -279,7 +260,6 @@ class MockPolylinesController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void changePolylines(Set<_i4.Polyline>? polylinesToChange) => super.noSuchMethod( @@ -289,7 +269,6 @@ class MockPolylinesController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removePolylines(Set<_i4.PolylineId>? polylineIdsToRemove) => super.noSuchMethod( @@ -299,7 +278,6 @@ class MockPolylinesController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void bindToMap( int? mapId, @@ -327,7 +305,6 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { returnValue: <_i4.MarkerId, _i3.MarkerController>{}, returnValueForMissingStub: <_i4.MarkerId, _i3.MarkerController>{}, ) as Map<_i4.MarkerId, _i3.MarkerController>); - @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), @@ -340,7 +317,6 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { Invocation.getter(#googleMap), ), ) as _i2.GMap); - @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( @@ -349,14 +325,12 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { ), returnValueForMissingStub: null, ); - @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); - @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( @@ -365,25 +339,26 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { ), returnValueForMissingStub: null, ); - @override - void addMarkers(Set<_i4.Marker>? markersToAdd) => super.noSuchMethod( + _i5.Future addMarkers(Set<_i4.Marker>? markersToAdd) => + (super.noSuchMethod( Invocation.method( #addMarkers, [markersToAdd], ), - returnValueForMissingStub: null, - ); - + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override - void changeMarkers(Set<_i4.Marker>? markersToChange) => super.noSuchMethod( + _i5.Future changeMarkers(Set<_i4.Marker>? markersToChange) => + (super.noSuchMethod( Invocation.method( #changeMarkers, [markersToChange], ), - returnValueForMissingStub: null, - ); - + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override void removeMarkers(Set<_i4.MarkerId>? markerIdsToRemove) => super.noSuchMethod( @@ -393,7 +368,6 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { ), returnValueForMissingStub: null, ); - @override void showMarkerInfoWindow(_i4.MarkerId? markerId) => super.noSuchMethod( Invocation.method( @@ -402,7 +376,6 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { ), returnValueForMissingStub: null, ); - @override void hideMarkerInfoWindow(_i4.MarkerId? markerId) => super.noSuchMethod( Invocation.method( @@ -411,7 +384,6 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { ), returnValueForMissingStub: null, ); - @override bool isInfoWindowShown(_i4.MarkerId? markerId) => (super.noSuchMethod( Invocation.method( @@ -421,7 +393,6 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { returnValue: false, returnValueForMissingStub: false, ) as bool); - @override void bindToMap( int? mapId, @@ -456,7 +427,6 @@ class MockTileOverlaysController extends _i1.Mock Invocation.getter(#googleMap), ), ) as _i2.GMap); - @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( @@ -465,14 +435,12 @@ class MockTileOverlaysController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); - @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( @@ -481,7 +449,6 @@ class MockTileOverlaysController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void addTileOverlays(Set<_i4.TileOverlay>? tileOverlaysToAdd) => super.noSuchMethod( @@ -491,7 +458,6 @@ class MockTileOverlaysController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void changeTileOverlays(Set<_i4.TileOverlay>? tileOverlays) => super.noSuchMethod( @@ -501,7 +467,6 @@ class MockTileOverlaysController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void removeTileOverlays(Set<_i4.TileOverlayId>? tileOverlayIds) => super.noSuchMethod( @@ -511,7 +476,6 @@ class MockTileOverlaysController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void clearTileCache(_i4.TileOverlayId? tileOverlayId) => super.noSuchMethod( Invocation.method( @@ -520,7 +484,6 @@ class MockTileOverlaysController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void bindToMap( int? mapId, diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart index 36b4d11e07d..b84b267c042 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart @@ -361,10 +361,10 @@ void main() { }); testWidgets('isMarkerInfoWindowShown', (WidgetTester tester) async { - when(controller.isInfoWindowShown(any)).thenReturn(true); - const MarkerId markerId = MarkerId('testing-123'); + when(controller.isInfoWindowShown(markerId)).thenReturn(true); + await plugin.isMarkerInfoWindowShown(markerId, mapId: mapId); verify(controller.isInfoWindowShown(markerId)); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart index 3f84b40adc8..30d7b46859e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart @@ -9,6 +9,7 @@ import 'package:google_maps/google_maps.dart' as _i5; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' as _i2; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i4; +import 'package:google_maps_flutter_web/src/marker_clustering.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint @@ -94,42 +95,37 @@ class MockGoogleMapController extends _i1.Mock Invocation.getter(#configuration), ), ) as _i2.MapConfiguration); - @override - _i3.StreamController<_i2.MapEvent> get stream => (super.noSuchMethod( + _i3.StreamController<_i2.MapEvent> get stream => (super.noSuchMethod( Invocation.getter(#stream), - returnValue: _FakeStreamController_1<_i2.MapEvent>( + returnValue: _FakeStreamController_1<_i2.MapEvent>( this, Invocation.getter(#stream), ), returnValueForMissingStub: - _FakeStreamController_1<_i2.MapEvent>( + _FakeStreamController_1<_i2.MapEvent>( this, Invocation.getter(#stream), ), - ) as _i3.StreamController<_i2.MapEvent>); - + ) as _i3.StreamController<_i2.MapEvent>); @override - _i3.Stream<_i2.MapEvent> get events => (super.noSuchMethod( + _i3.Stream<_i2.MapEvent> get events => (super.noSuchMethod( Invocation.getter(#events), - returnValue: _i3.Stream<_i2.MapEvent>.empty(), - returnValueForMissingStub: _i3.Stream<_i2.MapEvent>.empty(), - ) as _i3.Stream<_i2.MapEvent>); - + returnValue: _i3.Stream<_i2.MapEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.MapEvent>.empty(), + ) as _i3.Stream<_i2.MapEvent>); @override bool get isInitialized => (super.noSuchMethod( Invocation.getter(#isInitialized), returnValue: false, returnValueForMissingStub: false, ) as bool); - @override List<_i5.MapTypeStyle> get styles => (super.noSuchMethod( Invocation.getter(#styles), returnValue: <_i5.MapTypeStyle>[], returnValueForMissingStub: <_i5.MapTypeStyle>[], ) as List<_i5.MapTypeStyle>); - @override void debugSetOverrides({ _i4.DebugCreateMapFunction? createMap, @@ -138,6 +134,7 @@ class MockGoogleMapController extends _i1.Mock _i4.CirclesController? circles, _i4.PolygonsController? polygons, _i4.PolylinesController? polylines, + _i6.ClusterManagersController? clusterManagers, _i4.TileOverlaysController? tileOverlays, }) => super.noSuchMethod( @@ -151,12 +148,12 @@ class MockGoogleMapController extends _i1.Mock #circles: circles, #polygons: polygons, #polylines: polylines, + #clusterManagers: clusterManagers, #tileOverlays: tileOverlays, }, ), returnValueForMissingStub: null, ); - @override void init() => super.noSuchMethod( Invocation.method( @@ -165,7 +162,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void updateMapConfiguration(_i2.MapConfiguration? update) => super.noSuchMethod( @@ -175,7 +171,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void updateStyles(List<_i5.MapTypeStyle>? styles) => super.noSuchMethod( Invocation.method( @@ -184,7 +179,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override _i3.Future<_i2.LatLngBounds> getVisibleRegion() => (super.noSuchMethod( Invocation.method( @@ -207,7 +201,6 @@ class MockGoogleMapController extends _i1.Mock ), )), ) as _i3.Future<_i2.LatLngBounds>); - @override _i3.Future<_i2.ScreenCoordinate> getScreenCoordinate(_i2.LatLng? latLng) => (super.noSuchMethod( @@ -232,7 +225,6 @@ class MockGoogleMapController extends _i1.Mock ), )), ) as _i3.Future<_i2.ScreenCoordinate>); - @override _i3.Future<_i2.LatLng> getLatLng(_i2.ScreenCoordinate? screenCoordinate) => (super.noSuchMethod( @@ -255,7 +247,6 @@ class MockGoogleMapController extends _i1.Mock ), )), ) as _i3.Future<_i2.LatLng>); - @override _i3.Future moveCamera(_i2.CameraUpdate? cameraUpdate) => (super.noSuchMethod( @@ -266,7 +257,6 @@ class MockGoogleMapController extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); - @override _i3.Future getZoomLevel() => (super.noSuchMethod( Invocation.method( @@ -276,7 +266,6 @@ class MockGoogleMapController extends _i1.Mock returnValue: _i3.Future.value(0.0), returnValueForMissingStub: _i3.Future.value(0.0), ) as _i3.Future); - @override void updateCircles(_i2.CircleUpdates? updates) => super.noSuchMethod( Invocation.method( @@ -285,7 +274,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void updatePolygons(_i2.PolygonUpdates? updates) => super.noSuchMethod( Invocation.method( @@ -294,7 +282,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void updatePolylines(_i2.PolylineUpdates? updates) => super.noSuchMethod( Invocation.method( @@ -303,16 +290,25 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override - void updateMarkers(_i2.MarkerUpdates? updates) => super.noSuchMethod( + _i3.Future updateMarkers(_i2.MarkerUpdates? updates) => + (super.noSuchMethod( Invocation.method( #updateMarkers, [updates], ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + void updateClusterManagers(_i2.ClusterManagerUpdates? updates) => + super.noSuchMethod( + Invocation.method( + #updateClusterManagers, + [updates], + ), returnValueForMissingStub: null, ); - @override void updateTileOverlays(Set<_i2.TileOverlay>? newOverlays) => super.noSuchMethod( @@ -322,7 +318,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void clearTileCache(_i2.TileOverlayId? id) => super.noSuchMethod( Invocation.method( @@ -331,7 +326,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void showInfoWindow(_i2.MarkerId? markerId) => super.noSuchMethod( Invocation.method( @@ -340,7 +334,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override void hideInfoWindow(_i2.MarkerId? markerId) => super.noSuchMethod( Invocation.method( @@ -349,7 +342,6 @@ class MockGoogleMapController extends _i1.Mock ), returnValueForMissingStub: null, ); - @override bool isInfoWindowShown(_i2.MarkerId? markerId) => (super.noSuchMethod( Invocation.method( @@ -359,7 +351,6 @@ class MockGoogleMapController extends _i1.Mock returnValue: false, returnValueForMissingStub: false, ) as bool); - @override void dispose() => super.noSuchMethod( Invocation.method( diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_clustering_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_clustering_test.dart new file mode 100644 index 00000000000..c34d9c53311 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_clustering_test.dart @@ -0,0 +1,170 @@ +// 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. + +// ignore_for_file: unnecessary_nullable_for_final_variable_declarations + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + GoogleMapsFlutterPlatform.instance.enableDebugInspection(); + final GoogleMapsFlutterPlatform plugin = GoogleMapsFlutterPlatform.instance; + final GoogleMapsInspectorPlatform inspector = + GoogleMapsInspectorPlatform.instance!; + + const LatLng mapCenter = LatLng(20, 20); + const CameraPosition initialCameraPosition = + CameraPosition(target: mapCenter); + + group('MarkersController', () { + const int testMapId = 33930; + + testWidgets('Marker clustering', (WidgetTester tester) async { + const ClusterManagerId clusterManagerId = ClusterManagerId('cluster 1'); + + final Set clusterManagers = { + const ClusterManager(clusterManagerId: clusterManagerId), + }; + + // Create the marker with clusterManagerId. + final Set initialMarkers = { + const Marker( + markerId: MarkerId('1'), + position: mapCenter, + clusterManagerId: clusterManagerId), + const Marker( + markerId: MarkerId('2'), + position: mapCenter, + clusterManagerId: clusterManagerId), + }; + + final Completer mapIdCompleter = Completer(); + + await _pumpMap( + tester, + plugin.buildViewWithConfiguration( + testMapId, (int id) => mapIdCompleter.complete(id), + widgetConfiguration: const MapWidgetConfiguration( + initialCameraPosition: initialCameraPosition, + textDirection: TextDirection.ltr, + ), + mapObjects: MapObjects( + clusterManagers: clusterManagers, markers: initialMarkers))); + + final int mapId = await mapIdCompleter.future; + expect(mapId, equals(testMapId)); + + addTearDown(() => plugin.dispose(mapId: mapId)); + + final LatLng latlon = await plugin + .getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId); + debugPrint(latlon.toString()); + + final List clusters = + await waitForValueMatchingPredicate>( + tester, + () async => inspector.getClusters( + mapId: mapId, clusterManagerId: clusterManagerId), + (List clusters) => clusters.isNotEmpty) ?? + []; + + expect(clusters.length, 1); + expect(clusters[0].markerIds.length, 2); + + // Copy only the first marker with null clusterManagerId. + // This means that both markers should be removed from the cluster. + final Set updatedMarkers = { + _copyMarkerWithClusterManagerId(initialMarkers.first, null) + }; + + final MarkerUpdates markerUpdates = + MarkerUpdates.from(initialMarkers, updatedMarkers); + await plugin.updateMarkers(markerUpdates, mapId: mapId); + + final List updatedClusters = + await waitForValueMatchingPredicate>( + tester, + () async => inspector.getClusters( + mapId: mapId, clusterManagerId: clusterManagerId), + (List clusters) => clusters.isNotEmpty) ?? + []; + + expect(updatedClusters.length, 0); + }); + }); +} + +// Repeatedly checks an asynchronous value against a test condition, waiting +// one frame between each check, returing the value if it passes the predicate +// before [maxTries] is reached. +// +// Returns null if the predicate is never satisfied. +// +// This is useful for cases where the Maps SDK has some internally +// asynchronous operation that we don't have visibility into (e.g., native UI +// animations). +Future waitForValueMatchingPredicate(WidgetTester tester, + Future Function() getValue, bool Function(T) predicate, + {int maxTries = 100}) async { + for (int i = 0; i < maxTries; i++) { + final T value = await getValue(); + if (predicate(value)) { + return value; + } + await tester.pump(); + } + return null; +} + +Marker _copyMarkerWithClusterManagerId( + Marker marker, ClusterManagerId? clusterManagerId) { + return Marker( + markerId: marker.markerId, + alpha: marker.alpha, + anchor: marker.anchor, + consumeTapEvents: marker.consumeTapEvents, + draggable: marker.draggable, + flat: marker.flat, + icon: marker.icon, + infoWindow: marker.infoWindow, + position: marker.position, + rotation: marker.rotation, + visible: marker.visible, + zIndex: marker.zIndex, + onTap: marker.onTap, + onDragStart: marker.onDragStart, + onDrag: marker.onDrag, + onDragEnd: marker.onDragEnd, + clusterManagerId: clusterManagerId, + ); +} + +/// Pumps a [map] widget in [tester] of a certain [size], then waits until it settles. +Future _pumpMap(WidgetTester tester, Widget map, + [Size size = const Size.square(200)]) async { + await tester.pumpWidget(_wrapMap(map, size)); + await tester.pumpAndSettle(); +} + +/// Wraps a [map] in a bunch of widgets so it renders in all platforms. +/// +/// An optional [size] can be passed. +Widget _wrapMap(Widget map, [Size size = const Size.square(200)]) { + return MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox.fromSize( + size: size, + child: map, + ), + ), + ), + ); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart index d965435b3a5..f068ec8db30 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart @@ -5,12 +5,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; +import 'package:google_maps_flutter_web/src/marker_clustering.dart'; // ignore: implementation_imports import 'package:google_maps_flutter_web/src/utils.dart'; import 'package:http/http.dart' as http; @@ -25,12 +25,17 @@ void main() { group('MarkersController', () { late StreamController> events; late MarkersController controller; + late ClusterManagersController clusterManagersController; late gmaps.GMap map; setUp(() { events = StreamController>(); - controller = MarkersController(stream: events); + + clusterManagersController = ClusterManagersController(stream: events); + controller = MarkersController( + stream: events, clusterManagersController: clusterManagersController); map = gmaps.GMap(createDivElement()); + clusterManagersController.bindToMap(123, map); controller.bindToMap(123, map); }); @@ -40,7 +45,7 @@ void main() { const Marker(markerId: MarkerId('2')), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers.length, 2); expect(controller.markers, contains(const MarkerId('1'))); @@ -55,7 +60,7 @@ void main() { final Set markers = { const Marker(markerId: MarkerId('1')), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); marker = controller.markers[const MarkerId('1')]?.marker; expect(marker, isNotNull); @@ -75,7 +80,7 @@ void main() { position: LatLng(42, 54), ), }; - controller.changeMarkers(updatedMarkers); + await controller.changeMarkers(updatedMarkers); expect(controller.markers.length, 1); marker = controller.markers[const MarkerId('1')]?.marker; @@ -100,7 +105,7 @@ void main() { position: LatLng(42, 54), ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); marker = controller.markers[const MarkerId('1')]?.marker; expect(marker, isNotNull); @@ -118,7 +123,7 @@ void main() { draggable: true, ), }; - controller.changeMarkers(updatedMarkers); + await controller.changeMarkers(updatedMarkers); expect(controller.markers.length, 1); marker = controller.markers[const MarkerId('1')]?.marker; @@ -138,7 +143,7 @@ void main() { const Marker(markerId: MarkerId('3')), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers.length, 3); @@ -164,7 +169,7 @@ void main() { ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse); @@ -190,7 +195,7 @@ void main() { infoWindow: InfoWindow(title: 'Title', snippet: 'Snippet'), ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse); expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isFalse); @@ -206,18 +211,149 @@ void main() { expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isTrue); }); + testWidgets('markers with custom asset icon work', + (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/red_square.png', + imagePixelRatio: 1.0, + )), + }; + + await controller.addMarkers(markers); + + expect(controller.markers.length, 1); + final gmaps.Icon? icon = + controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; + expect(icon, isNotNull); + + final String assetUrl = icon!.url!; + expect(assetUrl, startsWith('assets')); + + final gmaps.Size size = icon.size!; + final gmaps.Size scaledSize = icon.scaledSize!; + + // asset size is 48x48 physical pixels + expect(size.width, 48); + expect(size.height, 48); + expect(scaledSize.width, 48); + expect(scaledSize.height, 48); + }); + + testWidgets('markers with custom asset icon and pixelratio work', + (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/red_square.png', + imagePixelRatio: 2.0, + )), + }; + + await controller.addMarkers(markers); + + expect(controller.markers.length, 1); + final gmaps.Icon? icon = + controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; + expect(icon, isNotNull); + + final String assetUrl = icon!.url!; + expect(assetUrl, startsWith('assets')); + + final gmaps.Size size = icon.size!; + final gmaps.Size scaledSize = icon.scaledSize!; + + // Asset size is 48x48 physical pixels, and with pixel ratio 2.0 it + // should be drawn with size 24x24 logical pixels. + expect(size.width, 24); + expect(size.height, 24); + expect(scaledSize.width, 24); + expect(scaledSize.height, 24); + }); + testWidgets('markers with custom asset icon with width and height work', + (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/red_square.png', + imagePixelRatio: 2.0, + width: 64, + height: 64, + )), + }; + + await controller.addMarkers(markers); + + expect(controller.markers.length, 1); + final gmaps.Icon? icon = + controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; + expect(icon, isNotNull); + + final String assetUrl = icon!.url!; + expect(assetUrl, startsWith('assets')); + + final gmaps.Size size = icon.size!; + final gmaps.Size scaledSize = icon.scaledSize!; + + // Asset size is 48x48 physical pixels, + // and scaled to requested 64x64 size. + expect(size.width, 64); + expect(size.height, 64); + expect(scaledSize.width, 64); + expect(scaledSize.height, 64); + }); + + testWidgets('markers with missing asset icon should not set size', + (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: AssetMapBitmap( + 'assets/broken_asset_name.png', + imagePixelRatio: 2.0, + )), + }; + + await controller.addMarkers(markers); + + expect(controller.markers.length, 1); + final gmaps.Icon? icon = + controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; + expect(icon, isNotNull); + + final String assetUrl = icon!.url!; + expect(assetUrl, startsWith('assets')); + + // For invalid assets, the size and scaledSize should be null. + expect(icon.size, null); + expect(icon.scaledSize, null); + }); + // https://github.com/flutter/flutter/issues/66622 testWidgets('markers with custom bitmap icon work', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); final Set markers = { Marker( markerId: const MarkerId('1'), - icon: BitmapDescriptor.fromBytes(bytes), + icon: BytesMapBitmap( + bytes, + imagePixelRatio: tester.view.devicePixelRatio, + ), ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers.length, 1); final gmaps.Icon? icon = @@ -231,20 +367,70 @@ void main() { expect(response.bodyBytes, bytes, reason: 'Bytes from the Icon blob must match bytes used to create Marker'); + + final gmaps.Size size = icon.size!; + final gmaps.Size scaledSize = icon.scaledSize!; + + // Icon size is 16x16 pixels, this should be automatically read from the + // bitmap and set to the icon size scaled to 8x8 using the + // given imagePixelRatio. + final int expectedSize = 16 ~/ tester.view.devicePixelRatio; + expect(size.width, expectedSize); + expect(size.height, expectedSize); + expect(scaledSize.width, expectedSize); + expect(scaledSize.height, expectedSize); + }); + + testWidgets('markers with custom bitmap icon and pixelratio work', + (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; + final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); + final Set markers = { + Marker( + markerId: const MarkerId('1'), + icon: BytesMapBitmap( + bytes, + imagePixelRatio: 1, + ), + ), + }; + + await controller.addMarkers(markers); + + expect(controller.markers.length, 1); + final gmaps.Icon? icon = + controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; + expect(icon, isNotNull); + + final gmaps.Size size = icon!.size!; + final gmaps.Size scaledSize = icon.scaledSize!; + + // Icon size is 16x16 pixels, this should be automatically read from the + // bitmap and set to the icon size and should not be changed as + // image pixel ratio is set to 1.0. + expect(size.width, 16); + expect(size.height, 16); + expect(scaledSize.width, 16); + expect(scaledSize.height, 16); }); // https://github.com/flutter/flutter/issues/73789 testWidgets('markers with custom bitmap icon pass size to sdk', (WidgetTester tester) async { + tester.view.devicePixelRatio = 2.0; final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); final Set markers = { Marker( markerId: const MarkerId('1'), - icon: BitmapDescriptor.fromBytes(bytes, size: const Size(20, 30)), + icon: BytesMapBitmap( + bytes, + width: 20, + height: 30, + ), ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers.length, 1); final gmaps.Icon? icon = @@ -273,7 +459,7 @@ void main() { ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers.length, 1); final HTMLElement? content = controller @@ -298,7 +484,7 @@ void main() { ), }; - controller.addMarkers(markers); + await controller.addMarkers(markers); expect(controller.markers.length, 1); final HTMLElement? content = controller diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 28381575ea0..85a89ed5bf9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 google_maps_flutter_web: path: ../ web: ^0.5.0 @@ -25,6 +25,10 @@ dev_dependencies: sdk: flutter mockito: 5.4.4 +flutter: + assets: + - assets/ + dependency_overrides: # Override the google_maps_flutter dependency on google_maps_flutter_web. # TODO(ditman): Unwind the circular dependency. This will create problems diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html b/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html index 3121d189b91..9cbd7be791d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html @@ -7,6 +7,7 @@ Browser Tests + diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index fe44d41aa48..cda20cf9f52 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -24,6 +24,7 @@ import 'package:web/web.dart'; import 'src/dom_window_extension.dart'; import 'src/google_maps_inspector_web.dart'; import 'src/map_styler.dart'; +import 'src/marker_clustering.dart'; import 'src/third_party/to_screen_location/to_screen_location.dart'; import 'src/types.dart'; import 'src/utils.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 0f73a7df044..ee9100ddff0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -12,6 +12,16 @@ final gmaps.LatLngBounds _nullGmapsLatLngBounds = // The TrustedType Policy used by this plugin. Used to sanitize InfoWindow contents. TrustedTypePolicy? _gmapsTrustedTypePolicy; +// A cache for image size Futures to reduce redundant image fetch requests. +// This cache should be always cleaned up after marker updates are processed. +final Map> _bitmapSizeFutureCache = + >{}; + +// A cache for blob URLs of bitmaps to avoid creating a new blob URL for the +// same bitmap instances. This cache should be always cleaned up after marker +// updates are processed. +final Map _bitmapBlobUrlCache = {}; + // Converts a [Color] into a valid CSS value #RRGGBB. String _getCssColor(Color color) { return '#${color.value.toRadixString(16).padLeft(8, '0').substring(2)}'; @@ -172,20 +182,22 @@ gmaps.LatLng _latLngToGmLatLng(LatLng latLng) { return gmaps.LatLng(latLng.latitude, latLng.longitude); } -LatLng _gmLatLngToLatLng(gmaps.LatLng latLng) { +/// Converts [gmaps.LatLng] to [LatLng]. +LatLng gmLatLngToLatLng(gmaps.LatLng latLng) { return LatLng(latLng.lat.toDouble(), latLng.lng.toDouble()); } -LatLngBounds _gmLatLngBoundsTolatLngBounds(gmaps.LatLngBounds latLngBounds) { +/// Converts a [gmaps.LatLngBounds] into a [LatLngBounds]. +LatLngBounds gmLatLngBoundsTolatLngBounds(gmaps.LatLngBounds latLngBounds) { return LatLngBounds( - southwest: _gmLatLngToLatLng(latLngBounds.southWest), - northeast: _gmLatLngToLatLng(latLngBounds.northEast), + southwest: gmLatLngToLatLng(latLngBounds.southWest), + northeast: gmLatLngToLatLng(latLngBounds.northEast), ); } CameraPosition _gmViewportToCameraPosition(gmaps.GMap map) { return CameraPosition( - target: _gmLatLngToLatLng(map.center ?? _nullGmapsLatLng), + target: gmLatLngToLatLng(map.center ?? _nullGmapsLatLng), bearing: map.heading?.toDouble() ?? 0, tilt: map.tilt?.toDouble() ?? 0, zoom: map.zoom?.toDouble() ?? 0, @@ -195,7 +207,6 @@ CameraPosition _gmViewportToCameraPosition(gmaps.GMap map) { // Convert plugin objects to gmaps.Options objects // TODO(ditman): Move to their appropriate objects, maybe make them copy constructors? // Marker.fromMarker(anotherMarker, moreOptions); - gmaps.InfoWindowOptions? _infoWindowOptionsFromMarker(Marker marker) { final String markerTitle = marker.infoWindow.title ?? ''; final String markerSnippet = marker.infoWindow.snippet ?? ''; @@ -262,20 +273,132 @@ gmaps.Size? _gmSizeFromIconConfig(List iconConfig, int sizeIndex) { final List? rawIconSize = iconConfig[sizeIndex] as List?; if (rawIconSize != null) { size = gmaps.Size( - rawIconSize[0] as num?, - rawIconSize[1] as num?, + rawIconSize[0]! as double, + rawIconSize[1]! as double, ); } } return size; } -// Converts a [BitmapDescriptor] into a [gmaps.Icon] that can be used in Markers. -gmaps.Icon? _gmIconFromBitmapDescriptor(BitmapDescriptor bitmapDescriptor) { - final List iconConfig = bitmapDescriptor.toJson() as List; +/// Sets the size of the Google Maps icon. +void _setIconSize({ + required Size size, + required gmaps.Icon icon, +}) { + final gmaps.Size gmapsSize = gmaps.Size(size.width, size.height); + icon.size = gmapsSize; + icon.scaledSize = gmapsSize; +} + +/// Determines the appropriate size for a bitmap based on its descriptor. +/// +/// This method returns the icon's size based on the provided [width] and +/// [height]. If both dimensions are null, the size is calculated using the +/// [imagePixelRatio] based on the actual size of the image fetched from the +/// [url]. If only one of the dimensions is provided, the other is calculated to +/// maintain the image's original aspect ratio. +Future _getBitmapSize(MapBitmap mapBitmap, String url) async { + final double? width = mapBitmap.width; + final double? height = mapBitmap.height; + if (width != null && height != null) { + // If both, width and height are set, return the provided dimensions. + return Size(width, height); + } else { + assert( + url.isNotEmpty, 'URL must not be empty when calculating dimensions.'); + + final Size? bitmapSize = await _bitmapSizeFutureCache.putIfAbsent(url, () { + return _fetchBitmapSize(url); + }); + + if (bitmapSize == null) { + // If bitmap size is null, the image is invalid, + // and the icon size cannot be calculated. + return null; + } + + double targetWidth = bitmapSize.width; + double targetHeight = bitmapSize.height; + if (width == null && height == null) { + // Width and height are not provided, so the imagePixelRatio is used to + // calculate the target size. + targetWidth /= mapBitmap.imagePixelRatio; + targetHeight /= mapBitmap.imagePixelRatio; + } else { + final double aspectRatio = bitmapSize.width / bitmapSize.height; + targetWidth = width ?? (height ?? bitmapSize.height) * aspectRatio; + targetHeight = height ?? (width ?? bitmapSize.width) / aspectRatio; + } + + // Return the calculated size. + return Size(targetWidth, targetHeight); + } +} +/// Fetches the size of the bitmap from a given URL and caches the result. +/// +/// This method attempts to fetch the image size for a given [url]. +Future _fetchBitmapSize(String url) async { + final HTMLImageElement image = HTMLImageElement()..src = url; + + // Wait for the onLoad or onError event. + await Future.any(>[image.onLoad.first, image.onError.first]); + + if (image.width == 0 || image.height == 0) { + // Complete with null for invalid images. + return null; + } + + // Complete with the image size for valid images. + return Size(image.width.toDouble(), image.height.toDouble()); +} + +/// Cleans up the caches used for bitmap size conversion and URL storage. +/// +/// This method should be called after marker updates are processed to ensure +/// that memory usage is optimized by removing completed or outdated cache +/// entries. +void _cleanUpBitmapConversionCaches() { + _bitmapSizeFutureCache.clear(); + _bitmapBlobUrlCache.clear(); +} + +// Converts a [BitmapDescriptor] into a [gmaps.Icon] that can be used in Markers. +Future _gmIconFromBitmapDescriptor( + BitmapDescriptor bitmapDescriptor) async { gmaps.Icon? icon; + if (bitmapDescriptor is MapBitmap) { + final String url = switch (bitmapDescriptor) { + (final BytesMapBitmap bytesMapBitmap) => + _bitmapBlobUrlCache.putIfAbsent(bytesMapBitmap.byteData.hashCode, () { + final Blob blob = + Blob([bytesMapBitmap.byteData.toJS].toJS); + return URL.createObjectURL(blob as JSObject); + }), + (final AssetMapBitmap assetMapBitmap) => + ui_web.assetManager.getAssetUrl(assetMapBitmap.assetName), + _ => throw UnimplementedError(), + }; + + icon = gmaps.Icon()..url = url; + + switch (bitmapDescriptor.bitmapScaling) { + case MapBitmapScaling.auto: + final Size? size = await _getBitmapSize(bitmapDescriptor, url); + if (size != null) { + _setIconSize(size: size, icon: icon); + } + case MapBitmapScaling.none: + break; + } + return icon; + } + + // The following code is for the deprecated BitmapDescriptor.fromBytes + // and BitmapDescriptor.fromAssetImage. + final List iconConfig = bitmapDescriptor.toJson() as List; if (iconConfig[0] == 'fromAssetImage') { assert(iconConfig.length >= 2); // iconConfig[2] contains the DPIs of the screen, but that information is @@ -313,16 +436,15 @@ gmaps.Icon? _gmIconFromBitmapDescriptor(BitmapDescriptor bitmapDescriptor) { ..scaledSize = size; } } - return icon; } // Computes the options for a new [gmaps.Marker] from an incoming set of options // [marker], and the existing marker registered with the map: [currentMarker]. -gmaps.MarkerOptions _markerOptionsFromMarker( +Future _markerOptionsFromMarker( Marker marker, gmaps.Marker? currentMarker, -) { +) async { return gmaps.MarkerOptions() ..position = gmaps.LatLng( marker.position.latitude, @@ -333,7 +455,7 @@ gmaps.MarkerOptions _markerOptionsFromMarker( ..visible = marker.visible ..opacity = marker.alpha ..draggable = marker.draggable - ..icon = _gmIconFromBitmapDescriptor(marker.icon); + ..icon = await _gmIconFromBitmapDescriptor(marker.icon); // TODO(ditman): Compute anchor properly, otherwise infowindows attach to the wrong spot. // Flat and Rotation are not supported directly on the web. } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index c60dd92a0ac..37c1e8a2fff 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -29,12 +29,17 @@ class GoogleMapController { _polygons = mapObjects.polygons, _polylines = mapObjects.polylines, _circles = mapObjects.circles, + _clusterManagers = mapObjects.clusterManagers, _tileOverlays = mapObjects.tileOverlays, _lastMapConfiguration = mapConfiguration { _circlesController = CirclesController(stream: _streamController); _polygonsController = PolygonsController(stream: _streamController); _polylinesController = PolylinesController(stream: _streamController); - _markersController = MarkersController(stream: _streamController); + _clusterManagersController = + ClusterManagersController(stream: _streamController); + _markersController = MarkersController( + stream: _streamController, + clusterManagersController: _clusterManagersController!); _tileOverlaysController = TileOverlaysController(); _updateStylesFromConfiguration(mapConfiguration); @@ -60,7 +65,9 @@ class GoogleMapController { final Set _polygons; final Set _polylines; final Set _circles; + final Set _clusterManagers; Set _tileOverlays; + // The configuration passed by the user, before converting to gmaps. // Caching this allows us to re-create the map faithfully when needed. MapConfiguration _lastMapConfiguration = const MapConfiguration(); @@ -118,13 +125,20 @@ class GoogleMapController { PolygonsController? _polygonsController; PolylinesController? _polylinesController; MarkersController? _markersController; + ClusterManagersController? _clusterManagersController; TileOverlaysController? _tileOverlaysController; + // Keeps track if _attachGeometryControllers has been called or not. bool _controllersBoundToMap = false; // Keeps track if the map is moving or not. bool _mapIsMoving = false; + /// The ClusterManagersController of this Map. Only for integration testing. + @visibleForTesting + ClusterManagersController? get clusterManagersController => + _clusterManagersController; + /// Overrides certain properties to install mocks defined during testing. @visibleForTesting void debugSetOverrides({ @@ -134,6 +148,7 @@ class GoogleMapController { CirclesController? circles, PolygonsController? polygons, PolylinesController? polylines, + ClusterManagersController? clusterManagers, TileOverlaysController? tileOverlays, }) { _overrideCreateMap = createMap; @@ -142,6 +157,7 @@ class GoogleMapController { _circlesController = circles ?? _circlesController; _polygonsController = polygons ?? _polygonsController; _polylinesController = polylines ?? _polylinesController; + _clusterManagersController = clusterManagers ?? _clusterManagersController; _tileOverlaysController = tileOverlays ?? _tileOverlaysController; } @@ -197,6 +213,8 @@ class GoogleMapController { _attachMapEvents(map); _attachGeometryControllers(map); + _initClustering(_clusterManagers); + // Now attach the geometry, traffic and any other layers... _renderInitialGeometry(); _setTrafficLayer(map, _lastMapConfiguration.trafficEnabled ?? false); @@ -211,13 +229,13 @@ class GoogleMapController { map.onClick.listen((gmaps.IconMouseEvent event) { assert(event.latLng != null); _streamController.add( - MapTapEvent(_mapId, _gmLatLngToLatLng(event.latLng!)), + MapTapEvent(_mapId, gmLatLngToLatLng(event.latLng!)), ); }); map.onRightclick.listen((gmaps.MapMouseEvent event) { assert(event.latLng != null); _streamController.add( - MapLongPressEvent(_mapId, _gmLatLngToLatLng(event.latLng!)), + MapLongPressEvent(_mapId, gmLatLngToLatLng(event.latLng!)), ); }); map.onBoundsChanged.listen((void _) { @@ -251,6 +269,8 @@ class GoogleMapController { 'Cannot attach a map to a null PolylinesController instance.'); assert(_markersController != null, 'Cannot attach a map to a null MarkersController instance.'); + assert(_clusterManagersController != null, + 'Cannot attach a map to a null ClusterManagersController instance.'); assert(_tileOverlaysController != null, 'Cannot attach a map to a null TileOverlaysController instance.'); @@ -258,11 +278,16 @@ class GoogleMapController { _polygonsController!.bindToMap(_mapId, map); _polylinesController!.bindToMap(_mapId, map); _markersController!.bindToMap(_mapId, map); + _clusterManagersController!.bindToMap(_mapId, map); _tileOverlaysController!.bindToMap(_mapId, map); _controllersBoundToMap = true; } + void _initClustering(Set clusterManagers) { + _clusterManagersController!.addClusterManagers(clusterManagers); + } + // Renders the initial sets of geometry. void _renderInitialGeometry() { assert( @@ -369,7 +394,7 @@ class GoogleMapController { await Future.value(_googleMap!.bounds) ?? _nullGmapsLatLngBounds; - return _gmLatLngBoundsTolatLngBounds(bounds); + return gmLatLngBoundsTolatLngBounds(bounds); } /// Returns the [ScreenCoordinate] for a given viewport [LatLng]. @@ -390,7 +415,7 @@ class GoogleMapController { final gmaps.LatLng latLng = _pixelToLatLng(_googleMap!, screenCoordinate.x, screenCoordinate.y); - return _gmLatLngToLatLng(latLng); + return gmLatLngToLatLng(latLng); } /// Applies a `cameraUpdate` to the current viewport. @@ -439,12 +464,23 @@ class GoogleMapController { } /// Applies [MarkerUpdates] to the currently managed markers. - void updateMarkers(MarkerUpdates updates) { + Future updateMarkers(MarkerUpdates updates) async { assert( _markersController != null, 'Cannot update markers after dispose().'); - _markersController?.addMarkers(updates.markersToAdd); - _markersController?.changeMarkers(updates.markersToChange); + await _markersController?.addMarkers(updates.markersToAdd); + await _markersController?.changeMarkers(updates.markersToChange); _markersController?.removeMarkers(updates.markerIdsToRemove); + _cleanUpBitmapConversionCaches(); + } + + /// Applies [ClusterManagerUpdates] to the currently managed cluster managers. + void updateClusterManagers(ClusterManagerUpdates updates) { + assert(_clusterManagersController != null, + 'Cannot update markers after dispose().'); + _clusterManagersController + ?.addClusterManagers(updates.clusterManagersToAdd); + _clusterManagersController + ?.removeClusterManagers(updates.clusterManagerIdsToRemove); } /// Updates the set of [TileOverlay]s. @@ -498,6 +534,7 @@ class GoogleMapController { _polygonsController = null; _polylinesController = null; _markersController = null; + _clusterManagersController = null; _tileOverlaysController = null; _streamController.close(); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index 805887f0d7f..16618b59a6e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -60,7 +60,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { MarkerUpdates markerUpdates, { required int mapId, }) async { - _map(mapId).updateMarkers(markerUpdates); + await _map(mapId).updateMarkers(markerUpdates); } /// Applies the passed in `polygonUpdates` to the `mapId`. @@ -98,6 +98,14 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { _map(mapId).updateTileOverlays(newTileOverlays); } + @override + Future updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) async { + _map(mapId).updateClusterManagers(clusterManagerUpdates); + } + @override Future clearTileCache( TileOverlayId tileOverlayId, { @@ -279,6 +287,11 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onClusterTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Future getStyleError({required int mapId}) async { return _map(mapId).lastStyleError; @@ -339,6 +352,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { void enableDebugInspection() { GoogleMapsInspectorPlatform.instance = GoogleMapsInspectorWeb( (int mapId) => _map(mapId).configuration, + (int mapId) => _map(mapId).clusterManagersController, ); } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart index 6d955315239..98b47430958 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart @@ -3,17 +3,25 @@ // found in the LICENSE file. import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'marker_clustering.dart'; /// Function that gets the [MapConfiguration] for a given `mapId`. typedef ConfigurationProvider = MapConfiguration Function(int mapId); +/// Function that gets the [ClusterManagersController] for a given `mapId`. +typedef ClusterManagersControllerProvider = ClusterManagersController? Function( + int mapId); + /// This platform implementation allows inspecting the running maps. class GoogleMapsInspectorWeb extends GoogleMapsInspectorPlatform { /// Build an "inspector" that is able to look into maps. - GoogleMapsInspectorWeb(ConfigurationProvider configurationProvider) - : _configurationProvider = configurationProvider; + GoogleMapsInspectorWeb(ConfigurationProvider configurationProvider, + ClusterManagersControllerProvider clusterManagersControllerProvider) + : _configurationProvider = configurationProvider, + _clusterManagersControllerProvider = clusterManagersControllerProvider; final ConfigurationProvider _configurationProvider; + final ClusterManagersControllerProvider _clusterManagersControllerProvider; @override Future areBuildingsEnabled({required int mapId}) async { @@ -85,4 +93,14 @@ class GoogleMapsInspectorWeb extends GoogleMapsInspectorPlatform { Future isTrafficEnabled({required int mapId}) async { return _configurationProvider(mapId).trafficEnabled ?? false; } + + @override + Future> getClusters({ + required int mapId, + required ClusterManagerId clusterManagerId, + }) async { + return _clusterManagersControllerProvider(mapId) + ?.getClusters(clusterManagerId) ?? + []; + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart index c1b0772a139..518dce6de77 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart @@ -15,9 +15,11 @@ class MarkerController { LatLngCallback? onDrag, LatLngCallback? onDragEnd, VoidCallback? onTap, + ClusterManagerId? clusterManagerId, }) : _marker = marker, _infoWindow = infoWindow, - _consumeTapEvents = consumeTapEvents { + _consumeTapEvents = consumeTapEvents, + _clusterManagerId = clusterManagerId { if (onTap != null) { marker.onClick.listen((gmaps.MapMouseEvent event) { onTap.call(); @@ -47,6 +49,8 @@ class MarkerController { final bool _consumeTapEvents; + final ClusterManagerId? _clusterManagerId; + final gmaps.InfoWindow? _infoWindow; bool _infoWindowShown = false; @@ -57,6 +61,9 @@ class MarkerController { /// Returns `true` if the [gmaps.InfoWindow] associated to this marker is being shown. bool get infoWindowShown => _infoWindowShown; + /// Returns [ClusterManagerId] if marker belongs to cluster. + ClusterManagerId? get clusterManagerId => _clusterManagerId; + /// Returns the [gmaps.Marker] associated to this controller. gmaps.Marker? get marker => _marker; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker_clustering.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker_clustering.dart new file mode 100644 index 00000000000..d4b5ed3bd70 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker_clustering.dart @@ -0,0 +1,139 @@ +// 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:async'; + +import 'package:google_maps/google_maps.dart' as gmaps; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +import '../google_maps_flutter_web.dart'; +import 'marker_clustering_js_interop.dart'; +import 'types.dart'; + +/// A controller class for managing marker clustering. +/// +/// This class maps [ClusterManager] objects to javascript [MarkerClusterer] +/// objects and provides an interface for adding and removing markers from +/// clusters. +class ClusterManagersController extends GeometryController { + /// Creates a new [ClusterManagersController] instance. + /// + /// The [stream] parameter is a required [StreamController] used for + /// emitting map events. + ClusterManagersController( + {required StreamController> stream}) + : _streamController = stream, + _clusterManagerIdToMarkerClusterer = + {}; + + // The stream over which cluster managers broadcast their events + final StreamController> _streamController; + + // A cache of [MarkerClusterer]s indexed by their [ClusterManagerId]. + final Map + _clusterManagerIdToMarkerClusterer; + + /// Adds a set of [ClusterManager] objects to the cache. + void addClusterManagers(Set clusterManagersToAdd) { + clusterManagersToAdd.forEach(_addClusterManager); + } + + void _addClusterManager(ClusterManager clusterManager) { + final MarkerClusterer markerClusterer = createMarkerClusterer( + googleMap, + (gmaps.MapMouseEvent event, MarkerClustererCluster cluster, + gmaps.GMap map) => + _clusterClicked( + clusterManager.clusterManagerId, event, cluster, map)); + + _clusterManagerIdToMarkerClusterer[clusterManager.clusterManagerId] = + markerClusterer; + markerClusterer.onAdd(); + } + + /// Removes a set of [ClusterManagerId]s from the cache. + void removeClusterManagers(Set clusterManagerIdsToRemove) { + clusterManagerIdsToRemove.forEach(_removeClusterManager); + } + + void _removeClusterManager(ClusterManagerId clusterManagerId) { + final MarkerClusterer? markerClusterer = + _clusterManagerIdToMarkerClusterer[clusterManagerId]; + if (markerClusterer != null) { + markerClusterer.clearMarkers(true); + markerClusterer.onRemove(); + } + _clusterManagerIdToMarkerClusterer.remove(clusterManagerId); + } + + /// Adds given [gmaps.Marker] to the [MarkerClusterer] with given + /// [ClusterManagerId]. + void addItem(ClusterManagerId clusterManagerId, gmaps.Marker marker) { + final MarkerClusterer? markerClusterer = + _clusterManagerIdToMarkerClusterer[clusterManagerId]; + if (markerClusterer != null) { + markerClusterer.addMarker(marker, true); + markerClusterer.render(); + } + } + + /// Removes given [gmaps.Marker] from the [MarkerClusterer] with given + /// [ClusterManagerId]. + void removeItem(ClusterManagerId clusterManagerId, gmaps.Marker? marker) { + if (marker != null) { + final MarkerClusterer? markerClusterer = + _clusterManagerIdToMarkerClusterer[clusterManagerId]; + if (markerClusterer != null) { + markerClusterer.removeMarker(marker, true); + markerClusterer.render(); + } + } + } + + /// Returns list of clusters in [MarkerClusterer] with given + /// [ClusterManagerId]. + List getClusters(ClusterManagerId clusterManagerId) { + final MarkerClusterer? markerClusterer = + _clusterManagerIdToMarkerClusterer[clusterManagerId]; + if (markerClusterer != null) { + return markerClusterer.clusters + .map((MarkerClustererCluster cluster) => + _convertCluster(clusterManagerId, cluster)) + .toList(); + } + return []; + } + + void _clusterClicked( + ClusterManagerId clusterManagerId, + gmaps.MapMouseEvent event, + MarkerClustererCluster markerClustererCluster, + gmaps.GMap map) { + if (markerClustererCluster.count > 0 && + markerClustererCluster.bounds != null) { + final Cluster cluster = + _convertCluster(clusterManagerId, markerClustererCluster); + _streamController.add(ClusterTapEvent(mapId, cluster)); + } + } + + /// Converts [MarkerClustererCluster] to [Cluster]. + Cluster _convertCluster(ClusterManagerId clusterManagerId, + MarkerClustererCluster markerClustererCluster) { + final LatLng position = gmLatLngToLatLng(markerClustererCluster.position); + final LatLngBounds bounds = + gmLatLngBoundsTolatLngBounds(markerClustererCluster.bounds!); + + final List markerIds = markerClustererCluster.markers + .map((gmaps.Marker marker) => + MarkerId(marker.get('markerId')! as String)) + .toList(); + return Cluster( + clusterManagerId, + markerIds, + position: position, + bounds: bounds, + ); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker_clustering_js_interop.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker_clustering_js_interop.dart new file mode 100644 index 00000000000..e3bf42b4e71 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker_clustering_js_interop.dart @@ -0,0 +1,164 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(srujzs): Needed for https://github.com/dart-lang/sdk/issues/54801. Once +// we publish a version with a min SDK constraint that contains this fix, +// remove. +@JS() +library; + +import 'dart:js_interop'; + +import 'package:google_maps/google_maps.dart' as gmaps; + +/// A typedef representing a callback function for handling cluster tap events. +typedef ClusterClickHandler = void Function( + gmaps.MapMouseEvent, + MarkerClustererCluster, + gmaps.GMap, +); + +/// The [MarkerClustererOptions] object used to initialize [MarkerClusterer]. +/// +/// See: https://googlemaps.github.io/js-markerclusterer/interfaces/MarkerClustererOptions.html +@JS() +@anonymous +extension type MarkerClustererOptions._(JSObject _) implements JSObject { + /// Constructs a new [MarkerClustererOptions] object. + factory MarkerClustererOptions({ + gmaps.GMap? map, + List? markers, + ClusterClickHandler? onClusterClick, + }) => + MarkerClustererOptions._js( + map: map as JSAny?, + markers: markers?.cast().toJS ?? JSArray(), + onClusterClick: onClusterClick != null + ? ((JSAny event, MarkerClustererCluster cluster, JSAny map) => + onClusterClick(event as gmaps.MapMouseEvent, cluster, + map as gmaps.GMap)).toJS + : null, + ); + + external factory MarkerClustererOptions._js({ + JSAny? map, + JSArray markers, + JSFunction? onClusterClick, + }); + + /// Returns the [gmaps.GMap] object. + gmaps.GMap? get map => _map as gmaps.GMap?; + @JS('map') + external JSAny? get _map; + + /// Returns the list of [gmaps.Marker] objects. + List? get markers => _markers?.toDart.cast(); + @JS('markers') + external JSArray? get _markers; + + /// Returns the onClusterClick handler. + ClusterClickHandler? get onClusterClick => + _onClusterClick?.toDart as ClusterClickHandler?; + @JS('onClusterClick') + external JSExportedDartFunction? get _onClusterClick; +} + +/// The cluster object handled by the [MarkerClusterer]. +/// +/// https://googlemaps.github.io/js-markerclusterer/classes/Cluster.html +@JS('markerClusterer.Cluster') +extension type MarkerClustererCluster._(JSObject _) implements JSObject { + /// Getter for the cluster marker. + gmaps.Marker get marker => _marker as gmaps.Marker; + @JS('marker') + external JSAny get _marker; + + /// List of markers in the cluster. + List get markers => _markers.toDart.cast(); + @JS('markers') + external JSArray get _markers; + + /// The bounds of the cluster. + gmaps.LatLngBounds? get bounds => _bounds as gmaps.LatLngBounds?; + @JS('bounds') + external JSAny? get _bounds; + + /// The position of the cluster marker. + gmaps.LatLng get position => _position as gmaps.LatLng; + @JS('position') + external JSAny get _position; + + /// Get the count of **visible** markers. + external int get count; + + /// Deletes the cluster. + external void delete(); + + /// Adds a marker to the cluster. + void push(gmaps.Marker marker) => _push(marker as JSAny); + @JS('push') + external void _push(JSAny marker); +} + +/// The [MarkerClusterer] object used to cluster markers on the map. +/// +/// https://googlemaps.github.io/js-markerclusterer/classes/MarkerClusterer.html +@JS('markerClusterer.MarkerClusterer') +extension type MarkerClusterer._(JSObject _) implements JSObject { + /// Constructs a new [MarkerClusterer] object. + external MarkerClusterer(MarkerClustererOptions options); + + /// Adds a marker to be clustered by the [MarkerClusterer]. + void addMarker(gmaps.Marker marker, bool? noDraw) => + _addMarker(marker as JSAny, noDraw); + @JS('addMarker') + external void _addMarker(JSAny marker, bool? noDraw); + + /// Adds a list of markers to be clustered by the [MarkerClusterer]. + void addMarkers(List? markers, bool? noDraw) => + _addMarkers(markers?.cast().toJS, noDraw); + @JS('addMarkers') + external void _addMarkers(JSArray? markers, bool? noDraw); + + /// Removes a marker from the [MarkerClusterer]. + bool removeMarker(gmaps.Marker marker, bool? noDraw) => + _removeMarker(marker as JSAny, noDraw); + @JS('removeMarker') + external bool _removeMarker(JSAny marker, bool? noDraw); + + /// Removes a list of markers from the [MarkerClusterer]. + bool removeMarkers(List? markers, bool? noDraw) => + _removeMarkers(markers?.cast().toJS, noDraw); + @JS('removeMarkers') + external bool _removeMarkers(JSArray? markers, bool? noDraw); + + /// Clears all the markers from the [MarkerClusterer]. + external void clearMarkers(bool? noDraw); + + /// Called when the [MarkerClusterer] is added to the map. + external void onAdd(); + + /// Called when the [MarkerClusterer] is removed from the map. + external void onRemove(); + + /// Returns the list of clusters. + List get clusters => + _clusters.toDart.cast(); + @JS('clusters') + external JSArray get _clusters; + + /// Recalculates and draws all the marker clusters. + external void render(); +} + +/// Creates [MarkerClusterer] object with given [gmaps.GMap] and +/// [ClusterClickHandler]. +MarkerClusterer createMarkerClusterer( + gmaps.GMap map, ClusterClickHandler onClusterClickHandler) { + final MarkerClustererOptions options = MarkerClustererOptions( + map: map, + onClusterClick: onClusterClickHandler, + ); + return MarkerClusterer(options); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart index 26cb94f9ad2..8bcf8b7f942 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart @@ -9,7 +9,9 @@ class MarkersController extends GeometryController { /// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. MarkersController({ required StreamController> stream, + required ClusterManagersController clusterManagersController, }) : _streamController = stream, + _clusterManagersController = clusterManagersController, _markerIdToController = {}; // A cache of [MarkerController]s indexed by their [MarkerId]. @@ -18,6 +20,8 @@ class MarkersController extends GeometryController { // The stream over which markers broadcast their events final StreamController> _streamController; + final ClusterManagersController _clusterManagersController; + /// Returns the cache of [MarkerController]s. Test only. @visibleForTesting Map get markers => _markerIdToController; @@ -25,11 +29,11 @@ class MarkersController extends GeometryController { /// Adds a set of [Marker] objects to the cache. /// /// Wraps each [Marker] into its corresponding [MarkerController]. - void addMarkers(Set markersToAdd) { - markersToAdd.forEach(_addMarker); + Future addMarkers(Set markersToAdd) async { + await Future.wait(markersToAdd.map(_addMarker)); } - void _addMarker(Marker marker) { + Future _addMarker(Marker marker) async { final gmaps.InfoWindowOptions? infoWindowOptions = _infoWindowOptionsFromMarker(marker); gmaps.InfoWindow? gmInfoWindow; @@ -52,10 +56,21 @@ class MarkersController extends GeometryController { _markerIdToController[marker.markerId]?.marker; final gmaps.MarkerOptions markerOptions = - _markerOptionsFromMarker(marker, currentMarker); - final gmaps.Marker gmMarker = gmaps.Marker(markerOptions)..map = googleMap; + await _markerOptionsFromMarker(marker, currentMarker); + + final gmaps.Marker gmMarker = gmaps.Marker(markerOptions); + + gmMarker.set('markerId', marker.markerId.value); + + if (marker.clusterManagerId != null) { + _clusterManagersController.addItem(marker.clusterManagerId!, gmMarker); + } else { + gmMarker.map = googleMap; + } + final MarkerController controller = MarkerController( marker: gmMarker, + clusterManagerId: marker.clusterManagerId, infoWindow: gmInfoWindow, consumeTapEvents: marker.consumeTapEvents, onTap: () { @@ -76,24 +91,35 @@ class MarkersController extends GeometryController { } /// Updates a set of [Marker] objects with new options. - void changeMarkers(Set markersToChange) { - markersToChange.forEach(_changeMarker); + Future changeMarkers(Set markersToChange) async { + await Future.wait(markersToChange.map(_changeMarker)); } - void _changeMarker(Marker marker) { + Future _changeMarker(Marker marker) async { final MarkerController? markerController = _markerIdToController[marker.markerId]; if (markerController != null) { - final gmaps.MarkerOptions markerOptions = _markerOptionsFromMarker( - marker, - markerController.marker, - ); - final gmaps.InfoWindowOptions? infoWindow = - _infoWindowOptionsFromMarker(marker); - markerController.update( - markerOptions, - newInfoWindowContent: infoWindow?.content as HTMLElement?, - ); + final ClusterManagerId? oldClusterManagerId = + markerController.clusterManagerId; + final ClusterManagerId? newClusterManagerId = marker.clusterManagerId; + + if (oldClusterManagerId != newClusterManagerId) { + // If clusterManagerId changes. Remove existing marker and create new one. + _removeMarker(marker.markerId); + await _addMarker(marker); + } else { + final gmaps.MarkerOptions markerOptions = + await _markerOptionsFromMarker( + marker, + markerController.marker, + ); + final gmaps.InfoWindowOptions? infoWindow = + _infoWindowOptionsFromMarker(marker); + markerController.update( + markerOptions, + newInfoWindowContent: infoWindow?.content as HTMLElement?, + ); + } } } @@ -104,6 +130,10 @@ class MarkersController extends GeometryController { void _removeMarker(MarkerId markerId) { final MarkerController? markerController = _markerIdToController[markerId]; + if (markerController?.clusterManagerId != null) { + _clusterManagersController.removeItem( + markerController!.clusterManagerId!, markerController.marker); + } markerController?.remove(); _markerIdToController.remove(markerId); } @@ -151,7 +181,7 @@ class MarkersController extends GeometryController { void _onMarkerDragStart(MarkerId markerId, gmaps.LatLng latLng) { _streamController.add(MarkerDragStartEvent( mapId, - _gmLatLngToLatLng(latLng), + gmLatLngToLatLng(latLng), markerId, )); } @@ -159,7 +189,7 @@ class MarkersController extends GeometryController { void _onMarkerDrag(MarkerId markerId, gmaps.LatLng latLng) { _streamController.add(MarkerDragEvent( mapId, - _gmLatLngToLatLng(latLng), + gmLatLngToLatLng(latLng), markerId, )); } @@ -167,7 +197,7 @@ class MarkersController extends GeometryController { void _onMarkerDragEnd(MarkerId markerId, gmaps.LatLng latLng) { _streamController.add(MarkerDragEndEvent( mapId, - _gmLatLngToLatLng(latLng), + gmLatLngToLatLng(latLng), markerId, )); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index f5c17935406..76707dd7c1e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.5.6+2 +version: 0.5.8 environment: sdk: ^3.3.0 @@ -23,7 +23,7 @@ dependencies: flutter_web_plugins: sdk: flutter google_maps: ^7.1.0 - google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_platform_interface: ^2.7.0 sanitize_html: ^2.0.0 stream_transform: ^2.0.0 web: ^0.5.1 diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/google_sign_in/google_sign_in/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/google_sign_in/google_sign_in/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/google_sign_in/google_sign_in/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/google_sign_in/google_sign_in/example/android/build.gradle b/packages/google_sign_in/google_sign_in/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/google_sign_in/google_sign_in/example/android/build.gradle +++ b/packages/google_sign_in/google_sign_in/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/google_sign_in/google_sign_in/example/android/settings.gradle b/packages/google_sign_in/google_sign_in/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/google_sign_in/google_sign_in/example/android/settings.gradle +++ b/packages/google_sign_in/google_sign_in/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/google_sign_in/google_sign_in/example/macos/Runner/DebugProfile.entitlements b/packages/google_sign_in/google_sign_in/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/google_sign_in/google_sign_in/example/macos/Runner/DebugProfile.entitlements +++ b/packages/google_sign_in/google_sign_in/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/google_sign_in/google_sign_in/example/macos/Runner/Release.entitlements b/packages/google_sign_in/google_sign_in/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/google_sign_in/google_sign_in/example/macos/Runner/Release.entitlements +++ b/packages/google_sign_in/google_sign_in/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index 02f8a85b193..e0e0bf85262 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,3 +1,16 @@ +## 6.1.26 + +* Removes additional references to the v1 Android embedding. + +## 6.1.25 + +* Updates Guava to version 33.2.1. + +## 6.1.24 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 6.1.23 * Updates minSdkVersion to 19. diff --git a/packages/google_sign_in/google_sign_in_android/README.md b/packages/google_sign_in/google_sign_in_android/README.md index 7a5bcabbb16..aeaeb9df6e9 100644 --- a/packages/google_sign_in/google_sign_in_android/README.md +++ b/packages/google_sign_in/google_sign_in_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/google_sign_in -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle index 06b27de658e..777d4ef934c 100644 --- a/packages/google_sign_in/google_sign_in_android/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle @@ -60,7 +60,7 @@ android { dependencies { implementation 'com.google.android.gms:play-services-auth:21.0.0' - implementation 'com.google.guava:guava:32.0.1-android' + implementation 'com.google.guava:guava:33.2.1-android' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.0.0' } diff --git a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java index eb4635a8179..c3f03ab2e90 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java @@ -49,14 +49,6 @@ public class GoogleSignInPlugin implements FlutterPlugin, ActivityAware { private @Nullable BinaryMessenger messenger; private ActivityPluginBinding activityPluginBinding; - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - GoogleSignInPlugin instance = new GoogleSignInPlugin(); - instance.initInstance(registrar.messenger(), registrar.context(), new GoogleSignInWrapper()); - instance.setUpRegistrar(registrar); - } - @VisibleForTesting public void initInstance( @NonNull BinaryMessenger messenger, @@ -67,12 +59,6 @@ public void initInstance( GoogleSignInApi.setup(messenger, delegate); } - @VisibleForTesting - @SuppressWarnings("deprecation") - public void setUpRegistrar(@NonNull PluginRegistry.Registrar registrar) { - delegate.setUpRegistrar(registrar); - } - private void dispose() { delegate = null; if (messenger != null) { @@ -354,9 +340,6 @@ public static class Delegate private static final String DEFAULT_GAMES_SIGN_IN = "SignInOption.games"; private final @NonNull Context context; - // Only set registrar for v1 embedder. - @SuppressWarnings("deprecation") - private PluginRegistry.Registrar registrar; // Only set activity for v2 embedder. Always access activity from getActivity() method. private @Nullable Activity activity; // TODO(stuartmorgan): See whether this can be replaced with background channels. @@ -372,19 +355,13 @@ public Delegate(@NonNull Context context, @NonNull GoogleSignInWrapper googleSig this.googleSignInWrapper = googleSignInWrapper; } - @SuppressWarnings("deprecation") - public void setUpRegistrar(@NonNull PluginRegistry.Registrar registrar) { - this.registrar = registrar; - registrar.addActivityResultListener(this); - } - public void setActivity(@Nullable Activity activity) { this.activity = activity; } // Only access activity with this method. public @Nullable Activity getActivity() { - return registrar != null ? registrar.activity() : activity; + return activity; } private void checkAndSetPendingOperation( diff --git a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInLegacyMethodChannelTest.java b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInLegacyMethodChannelTest.java index 4d8c79e22ad..cd5b92217c6 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInLegacyMethodChannelTest.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInLegacyMethodChannelTest.java @@ -22,6 +22,7 @@ import com.google.android.gms.common.api.Scope; import com.google.android.gms.common.api.Status; import com.google.android.gms.tasks.Task; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -48,10 +49,7 @@ public class GoogleSignInLegacyMethodChannelTest { @Mock GoogleSignInAccount account; @Mock GoogleSignInClient mockClient; @Mock Task mockSignInTask; - - @SuppressWarnings("deprecation") - @Mock - PluginRegistry.Registrar mockRegistrar; + @Mock ActivityPluginBinding mockActivityPluginBinding; private GoogleSignInPlugin plugin; private AutoCloseable mockCloseable; @@ -59,13 +57,11 @@ public class GoogleSignInLegacyMethodChannelTest { @Before public void setUp() { mockCloseable = MockitoAnnotations.openMocks(this); - when(mockRegistrar.messenger()).thenReturn(mockMessenger); - when(mockRegistrar.context()).thenReturn(mockContext); - when(mockRegistrar.activity()).thenReturn(mockActivity); when(mockContext.getResources()).thenReturn(mockResources); + when(mockActivityPluginBinding.getActivity()).thenReturn(mockActivity); plugin = new GoogleSignInPlugin(); - plugin.initInstance(mockRegistrar.messenger(), mockRegistrar.context(), mockGoogleSignIn); - plugin.setUpRegistrar(mockRegistrar); + plugin.initInstance(mockMessenger, mockContext, mockGoogleSignIn); + plugin.onAttachedToActivity(mockActivityPluginBinding); } @After @@ -124,7 +120,7 @@ public void requestScopes_ReturnsFalseIfPermissionDenied() { ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); + verify(mockActivityPluginBinding).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); @@ -149,7 +145,7 @@ public void requestScopes_ReturnsTrueIfPermissionGranted() { ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); + verify(mockActivityPluginBinding).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); @@ -172,7 +168,7 @@ public void requestScopes_mayBeCalledRepeatedly_ifAlreadyGranted() { ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); + verify(mockActivityPluginBinding).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); @@ -198,7 +194,7 @@ public void requestScopes_mayBeCalledRepeatedly_ifNotSignedIn() { ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); + verify(mockActivityPluginBinding).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(null); diff --git a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java index b317d8042f1..6089a81b354 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java @@ -23,7 +23,6 @@ import com.google.android.gms.common.api.Status; import com.google.android.gms.tasks.Task; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugins.googlesignin.Messages.FlutterError; import io.flutter.plugins.googlesignin.Messages.InitParams; import java.util.Collections; @@ -50,22 +49,14 @@ public class GoogleSignInTest { @Mock GoogleSignInClient mockClient; @Mock Task mockSignInTask; - @SuppressWarnings("deprecation") - @Mock - PluginRegistry.Registrar mockRegistrar; - private GoogleSignInPlugin.Delegate plugin; private AutoCloseable mockCloseable; @Before public void setUp() { mockCloseable = MockitoAnnotations.openMocks(this); - when(mockRegistrar.messenger()).thenReturn(mockMessenger); - when(mockRegistrar.context()).thenReturn(mockContext); - when(mockRegistrar.activity()).thenReturn(mockActivity); when(mockContext.getResources()).thenReturn(mockResources); - plugin = new GoogleSignInPlugin.Delegate(mockRegistrar.context(), mockGoogleSignIn); - plugin.setUpRegistrar(mockRegistrar); + plugin = new GoogleSignInPlugin.Delegate(mockContext, mockGoogleSignIn); } @After @@ -101,6 +92,7 @@ public void requestScopes_ResultTrueIfAlreadyGranted() { @Test public void requestScopes_RequestsPermissionIfNotGranted() { Scope requestedScope = new Scope("requestedScope"); + plugin.setActivity(mockActivity); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); @@ -114,17 +106,13 @@ public void requestScopes_RequestsPermissionIfNotGranted() { @Test public void requestScopes_ReturnsFalseIfPermissionDenied() { Scope requestedScope = new Scope("requestedScope"); - ArgumentCaptor captor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); - PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.requestScopes(Collections.singletonList("requestedScope"), boolResult); - listener.onActivityResult( + plugin.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_CANCELED, new Intent()); @@ -135,17 +123,13 @@ public void requestScopes_ReturnsFalseIfPermissionDenied() { @Test public void requestScopes_ReturnsTrueIfPermissionGranted() { Scope requestedScope = new Scope("requestedScope"); - ArgumentCaptor captor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); - PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.requestScopes(Collections.singletonList("requestedScope"), boolResult); - listener.onActivityResult( + plugin.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); verify(boolResult).success(true); @@ -155,20 +139,16 @@ public void requestScopes_ReturnsTrueIfPermissionGranted() { public void requestScopes_mayBeCalledRepeatedly_ifAlreadyGranted() { List requestedScopes = Collections.singletonList("requestedScope"); Scope requestedScope = new Scope("requestedScope"); - ArgumentCaptor captor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); - PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.requestScopes(requestedScopes, boolResult); - listener.onActivityResult( + plugin.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); plugin.requestScopes(requestedScopes, boolResult); - listener.onActivityResult( + plugin.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); verify(boolResult, times(2)).success(true); @@ -177,18 +157,14 @@ public void requestScopes_mayBeCalledRepeatedly_ifAlreadyGranted() { @Test public void requestScopes_mayBeCalledRepeatedly_ifNotSignedIn() { List requestedScopes = Collections.singletonList("requestedScope"); - ArgumentCaptor captor = - ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); - verify(mockRegistrar).addActivityResultListener(captor.capture()); - PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(null); plugin.requestScopes(requestedScopes, boolResult); - listener.onActivityResult( + plugin.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); plugin.requestScopes(requestedScopes, boolResult); - listener.onActivityResult( + plugin.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(Throwable.class); diff --git a/packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/google_sign_in/google_sign_in_android/example/android/build.gradle b/packages/google_sign_in/google_sign_in_android/example/android/build.gradle index 6091524e1f1..dc46ed04d80 100644 --- a/packages/google_sign_in/google_sign_in_android/example/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/google_sign_in/google_sign_in_android/example/android/settings.gradle b/packages/google_sign_in/google_sign_in_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/google_sign_in/google_sign_in_android/example/android/settings.gradle +++ b/packages/google_sign_in/google_sign_in_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml index 9452d74744c..11b6f074272 100644 --- a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Example of Google Sign-In plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index 61f9204f0cd..8d583e406d7 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -2,11 +2,11 @@ name: google_sign_in_android description: Android implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.1.23 +version: 6.1.26 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: 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 20cf221aeb5..3fe0f7a1307 100644 --- a/packages/google_sign_in/google_sign_in_ios/README.md +++ b/packages/google_sign_in/google_sign_in_ios/README.md @@ -14,7 +14,7 @@ should add it to your `pubspec.yaml` as usual. ### macOS setup The GoogleSignIn SDK requires keychain sharing to be enabled, by [adding the -following entitlements](https://docs.flutter.dev/platform-integration/macos/building#entitlements-and-the-app-sandbox): +following entitlements](https://flutter.dev/to/macos-entitlements): ```xml keychain-access-groups @@ -27,7 +27,7 @@ Without this step, the plugin will throw a `keychain error` `PlatformException` when trying to sign in. [1]: https://pub.dev/packages/google_sign_in -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin ### iOS integration diff --git a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/DebugProfile.entitlements b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/DebugProfile.entitlements +++ b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/Release.entitlements b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/Release.entitlements +++ b/packages/google_sign_in/google_sign_in_ios/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md index 322e22f1d9a..c7ca3e77143 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.4.5 diff --git a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml index 2e8654243ed..85a11820511 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.4.5 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: 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 49d02d368ba..c8eb27023c7 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,7 @@ +## 0.12.4+1 + +* Fixes README.md typo. + ## 0.12.4 * Updates dependencies to `web: ^0.5.0` and `google_identity_services_web: ^0.3.1`. diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md index 0777e8e0eac..f51d1bd6359 100644 --- a/packages/google_sign_in/google_sign_in_web/README.md +++ b/packages/google_sign_in/google_sign_in_web/README.md @@ -18,7 +18,7 @@ plugin required a major version update. The **Google Sign-In JavaScript for Web JS SDK** is set to be deprecated after March 31, 2023. **Google Identity Services (GIS) SDK** is the new solution to -quickly and easily sign users into your app suing their Google accounts. +quickly and easily sign users into your app using their Google accounts. * In the GIS SDK, Authentication and Authorization are now two separate concerns. * Authentication (information about the current user) flows will not @@ -145,7 +145,7 @@ The GIS SDK limits authorization token duration to one hour (3600 seconds). ### Import the package -This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +This package is [endorsed](https://flutter.dev/to/endorsed-federated-plugin), which means you can simply use `google_sign_in` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. diff --git a/packages/google_sign_in/google_sign_in_web/example/README.md b/packages/google_sign_in/google_sign_in_web/example/README.md index 45af13eddee..9140bb80e8a 100644 --- a/packages/google_sign_in/google_sign_in_web/example/README.md +++ b/packages/google_sign_in/google_sign_in_web/example/README.md @@ -12,10 +12,10 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. # button_tester.dart diff --git a/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart b/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart index 4def097e52c..02b4346e9b8 100644 --- a/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart +++ b/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart @@ -4,17 +4,17 @@ import 'package:flutter/material.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; -import 'package:google_sign_in_web/google_sign_in_web.dart'; +import 'package:google_sign_in_web/web_only.dart'; import 'src/button_configuration_column.dart'; -// The instance of the plugin is automatically created by Flutter before calling -// our main code, let's grab it directly from the Platform interface of the plugin. -final GoogleSignInPlugin _plugin = - GoogleSignInPlatform.instance as GoogleSignInPlugin; +// Let's use the Platform Interface directly, no need to use anything web-specific +// from it. (In a normal app, we'd use the plugin interface!) +// All the web-specific imports come from the `web_only.dart` library. +final GoogleSignInPlatform _platform = GoogleSignInPlatform.instance; Future main() async { - await _plugin.initWithParams(const SignInInitParameters( + await _platform.initWithParams(const SignInInitParameters( clientId: 'your-client_id.apps.googleusercontent.com', )); runApp( @@ -41,7 +41,7 @@ class _ButtonConfiguratorState extends State { @override void initState() { super.initState(); - _plugin.userDataEvents?.listen((GoogleSignInUserData? userData) { + _platform.userDataEvents?.listen((GoogleSignInUserData? userData) { setState(() { _userData = userData; }); @@ -49,7 +49,7 @@ class _ButtonConfiguratorState extends State { } void _handleSignOut() { - _plugin.signOut(); + _platform.signOut(); setState(() { // signOut does not broadcast through the userDataEvents, so we fake it. _userData = null; @@ -70,7 +70,7 @@ class _ButtonConfiguratorState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ if (_userData == null) - _plugin.renderButton(configuration: _buttonConfiguration), + renderButton(configuration: _buttonConfiguration), if (_userData != null) ...[ Text('Hello, ${_userData!.displayName}!'), ElevatedButton( diff --git a/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart b/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart index 75ea0955979..19009caa436 100644 --- a/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart +++ b/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart @@ -3,13 +3,17 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:google_sign_in_web/google_sign_in_web.dart'; +import 'package:google_sign_in_web/web_only.dart'; /// Type of the onChange function for `ButtonConfiguration`. typedef OnWebConfigChangeFn = void Function(GSIButtonConfiguration newConfig); -/// (Incomplete) List of the locales that can be used to configure the button. -const List availableLocales = [ +/// The type of the widget builder function for each Card in the ListView builder +typedef CardBuilder = Widget Function( + GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange); + +// (Incomplete) List of the locales that can be used to configure the button. +const List _availableLocales = [ 'en_US', 'es_ES', 'pt_BR', @@ -18,6 +22,65 @@ const List availableLocales = [ 'de_DE', ]; +// The builder functions for the Cards that let users customize the button. +final List _cards = [ + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderLocaleCard( + value: currentConfig?.locale ?? 'en_US', + locales: _availableLocales, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderMinimumWidthCard( + value: currentConfig?.minimumWidth, + max: 500, + actualMax: 400, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonType', + values: GSIButtonType.values, + selected: currentConfig?.type ?? GSIButtonType.standard, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonShape', + values: GSIButtonShape.values, + selected: currentConfig?.shape ?? GSIButtonShape.rectangular, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonSize', + values: GSIButtonSize.values, + selected: currentConfig?.size ?? GSIButtonSize.large, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonTheme', + values: GSIButtonTheme.values, + selected: currentConfig?.theme ?? GSIButtonTheme.outline, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonText', + values: GSIButtonText.values, + selected: currentConfig?.text ?? GSIButtonText.signinWith, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonLogoAlignment', + values: GSIButtonLogoAlignment.values, + selected: currentConfig?.logoAlignment ?? GSIButtonLogoAlignment.left, + onChanged: _onChanged(currentConfig, onChange), + ), +]; + /// Renders a Scrollable Column widget that allows the user to see (and change) a ButtonConfiguration. Widget renderWebButtonConfiguration( GSIButtonConfiguration? currentConfig, { @@ -25,116 +88,80 @@ Widget renderWebButtonConfiguration( }) { final ScrollController scrollController = ScrollController(); return Scrollbar( - controller: scrollController, - thumbVisibility: true, - interactive: true, - child: SingleChildScrollView( - controller: scrollController, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _renderLocaleCard( - value: currentConfig?.locale, - locales: availableLocales, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderMinimumWidthCard( - value: currentConfig?.minimumWidth, - max: 500, - actualMax: 400, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonType', - values: GSIButtonType.values, - selected: currentConfig?.type, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonShape', - values: GSIButtonShape.values, - selected: currentConfig?.shape, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonSize', - values: GSIButtonSize.values, - selected: currentConfig?.size, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonTheme', - values: GSIButtonTheme.values, - selected: currentConfig?.theme, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonText', - values: GSIButtonText.values, - selected: currentConfig?.text, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonLogoAlignment', - values: GSIButtonLogoAlignment.values, - selected: currentConfig?.logoAlignment, - onChanged: - _onChanged(currentConfig, onChange), - ), - ], - ))); + controller: scrollController, + thumbVisibility: true, + interactive: true, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 250), + child: ListView.builder( + controller: scrollController, + itemCount: _cards.length, + itemBuilder: (BuildContext _, int index) => + _cards[index](currentConfig, onChange), + ), + ), + ); } /// Renders a Config card with a dropdown of locales. -Widget _renderLocaleCard( - {String? value, - required List locales, - void Function(String?)? onChanged}) { - return _renderConfigCard(title: 'locale', children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: DropdownButton( - items: locales - .map((String locale) => DropdownMenuItem( - value: locale, - child: Text(locale), - )) - .toList(), - value: value, - onChanged: onChanged, - isExpanded: true, - // padding: const EdgeInsets.symmetric(horizontal: 16), // Prefer padding here! +Widget _renderLocaleCard({ + String? value, + required List locales, + void Function(String?)? onChanged, +}) { + return _renderConfigCard( + title: 'locale', + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: DropdownButton( + items: locales + .map((String locale) => DropdownMenuItem( + value: locale, + child: Text(locale), + )) + .toList(), + value: value, + onChanged: onChanged, + isExpanded: true, + // padding: const EdgeInsets.symmetric(horizontal: 16), // Prefer padding here! + ), ), - ), - ]); + ], + ); } /// Renders a card with a slider -Widget _renderMinimumWidthCard( - {double? value, - double min = 0, - double actualMax = 10, - double max = 11, - void Function(double)? onChanged}) { - return _renderConfigCard(title: 'minimumWidth', children: [ - Slider( - label: value?.toString() ?? 'null', - value: value ?? 0, - min: min, - max: max, - secondaryTrackValue: actualMax, - onChanged: onChanged, - divisions: 10, - ) - ]); +Widget _renderMinimumWidthCard({ + double? value, + double min = 0, + double actualMax = 10, + double max = 11, + void Function(double)? onChanged, +}) { + return _renderConfigCard( + title: 'minimumWidth', + children: [ + Slider( + label: value?.toString() ?? 'null', + value: value ?? 0, + min: min, + max: max, + secondaryTrackValue: actualMax, + onChanged: onChanged, + divisions: 10, + ) + ], + ); } /// Renders a Config Card with the values of an Enum as radio buttons. -Widget _renderRadioListTileCard( - {required String title, - required List values, - T? selected, - void Function(T?)? onChanged}) { +Widget _renderRadioListTileCard({ + required String title, + required List values, + T? selected, + void Function(T?)? onChanged, +}) { return _renderConfigCard( title: title, children: values @@ -150,29 +177,32 @@ Widget _renderRadioListTileCard( } /// Renders a Card where we render some `children` that change config. -Widget _renderConfigCard( - {required String title, required List children}) { - return Container( - constraints: const BoxConstraints(maxWidth: 200), - child: Card( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - dense: true, +Widget _renderConfigCard({ + required String title, + required List children, +}) { + return Card( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold), ), - ...children, - ], - ))); + dense: true, + ), + ...children, + ], + ), + ); } /// Sets a `value` into an `old` configuration object. -GSIButtonConfiguration _copyConfigWith( - GSIButtonConfiguration? old, Object? value) { +GSIButtonConfiguration _copyConfigWith( + GSIButtonConfiguration? old, + T? value, +) { return GSIButtonConfiguration( locale: value is String ? value : old?.locale, minimumWidth: @@ -188,11 +218,13 @@ GSIButtonConfiguration _copyConfigWith( /// Returns a function that modifies the `current` configuration with a `value`, then calls `fn` with it. void Function(T?)? _onChanged( - GSIButtonConfiguration? current, OnWebConfigChangeFn? fn) { + GSIButtonConfiguration? current, + OnWebConfigChangeFn? fn, +) { if (fn == null) { return null; } return (T? value) { - fn(_copyConfigWith(current, value)); + fn(_copyConfigWith(current, value)); }; } 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 b7ed804cc93..6c4409cdab3 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.4 +version: 0.12.4+1 environment: sdk: ^3.3.0 diff --git a/packages/image_picker/image_picker/AUTHORS b/packages/image_picker/image_picker/AUTHORS index 493a0b4ef9c..3b9449f8d51 100644 --- a/packages/image_picker/image_picker/AUTHORS +++ b/packages/image_picker/image_picker/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +LinXunFeng diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index c9f4b3b43fc..29fb408f8b4 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.1.2 + +* Adds comment for the limit parameter. + +## 1.1.1 + +* Updates documentation to note that Android Photo Picker use is not optional on Android 13+. + ## 1.1.0 * Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` which limits diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index d9f965414b3..866adf58118 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -10,10 +10,7 @@ and taking new pictures with the camera. |-------------|---------|---------|-------|--------|---------------------------------|-------------| | **Support** | SDK 21+ | iOS 12+ | Any | 10.14+ | [See `image_picker_for_web`](https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform) | Windows 10+ | -## Installation - -First, add `image_picker` as a -[dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). +## Setup ### iOS @@ -95,9 +92,9 @@ responsibility to move it to a more permanent location. #### Android Photo Picker -This package has optional +On Android 13 and above this package uses the [Android Photo Picker](https://developer.android.com/training/data-storage/shared/photopicker) -functionality. +. On Android 12 and below use of Android Photo Picker is optional. [Learn how to use it](https://pub.dev/packages/image_picker_android). #### Using `launchMode: singleInstance` @@ -158,7 +155,8 @@ encourage the community to build packages that implement Since the macOS implementation uses `file_selector`, you will need to add a filesystem access -[entitlement](https://docs.flutter.dev/platform-integration/macos/building#entitlements-and-the-app-sandbox): +[entitlement](https://flutter.dev/to/macos-entitlements): + ```xml com.apple.security.files.user-selected.read-only diff --git a/packages/image_picker/image_picker/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/image_picker/image_picker/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/image_picker/image_picker/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/image_picker/image_picker/example/android/build.gradle b/packages/image_picker/image_picker/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100755 --- a/packages/image_picker/image_picker/example/android/build.gradle +++ b/packages/image_picker/image_picker/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/image_picker/image_picker/example/android/settings.gradle b/packages/image_picker/image_picker/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100755 --- a/packages/image_picker/image_picker/example/android/settings.gradle +++ b/packages/image_picker/image_picker/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/image_picker/image_picker/example/macos/Runner/DebugProfile.entitlements b/packages/image_picker/image_picker/example/macos/Runner/DebugProfile.entitlements index 0ceee8dff19..04c9acf4af8 100644 --- a/packages/image_picker/image_picker/example/macos/Runner/DebugProfile.entitlements +++ b/packages/image_picker/image_picker/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/image_picker/image_picker/example/macos/Runner/Release.entitlements b/packages/image_picker/image_picker/example/macos/Runner/Release.entitlements index 18aff0ce43c..afa43e9be4f 100644 --- a/packages/image_picker/image_picker/example/macos/Runner/Release.entitlements +++ b/packages/image_picker/image_picker/example/macos/Runner/Release.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.files.user-selected.read-only diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index 246fe3a4ca1..3e51cc13dac 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -112,6 +112,9 @@ class ImagePicker { /// image types such as JPEG and on Android PNG and WebP, too. If compression is not /// supported for the image that is picked, a warning message will be logged. /// + /// The `limit` parameter modifies the maximum number of images that can be selected. + /// This value may be ignored by platforms that cannot support it. + /// /// Use `requestFullMetadata` (defaults to `true`) to control how much additional /// information the plugin tries to get. /// If `requestFullMetadata` is set to `true`, the plugin tries to get the full @@ -225,6 +228,9 @@ class ImagePicker { /// image types such as JPEG and on Android PNG and WebP, too. If compression is not /// supported for the image that is picked, a warning message will be logged. /// + /// The `limit` parameter modifies the maximum number of media that can be selected. + /// This value may be ignored by platforms that cannot support it. + /// /// Use `requestFullMetadata` (defaults to `true`) to control how much additional /// information the plugin tries to get. /// If `requestFullMetadata` is set to `true`, the plugin tries to get the full diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 5854c9f0211..ee854ea43ad 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 1.1.0 +version: 1.1.2 environment: sdk: ^3.3.0 diff --git a/packages/image_picker/image_picker_android/AUTHORS b/packages/image_picker/image_picker_android/AUTHORS index 57d4f75a1d3..e563a24b660 100644 --- a/packages/image_picker/image_picker_android/AUTHORS +++ b/packages/image_picker/image_picker_android/AUTHORS @@ -65,3 +65,4 @@ Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> André Sousa +LinXunFeng diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 9efc86c1c93..dde51eef810 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,24 @@ +## 0.8.12+3 + +* Update documentation to note that limit is not always supported. + +## 0.8.12+2 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 0.8.12+1 + +* Fixes another app crash case on Android 12+, and refactors getting of paths from intents. + +## 0.8.12 + +* Fixes app crashes on Android 12+ caused by selecting images with size 0. + +## 0.8.11 + +* Updates documentation to note that Android Photo Picker use is not optional on Android 13+. + ## 0.8.10 * Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` that sets a limit to how many media or image items can be selected. diff --git a/packages/image_picker/image_picker_android/README.md b/packages/image_picker/image_picker_android/README.md index 0e66803f4c4..a0bcc70294d 100755 --- a/packages/image_picker/image_picker_android/README.md +++ b/packages/image_picker/image_picker_android/README.md @@ -15,7 +15,9 @@ should add it to your `pubspec.yaml` as usual. ## Photo Picker -This package has optional Android Photo Picker functionality. +On Android 13 and above this packages uses the Android Photo Picker. + +On Android 12 and below this package has optional Android Photo Picker functionality. To use this feature, add the following code to your app before calling any `image_picker` APIs: @@ -31,5 +33,8 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface. } ``` +In addition, `ImagePickerAndroid.useAndroidPhotoPicker` must be set to `true` to use the `limit` functionality. It is implemented based on [`ActivityResultContract`][3], so it can only be ensured to take effect on Android 13 or above. Otherwise, it depends on whether the corresponding system app supports it. + [1]: https://pub.dev/packages/image_picker -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin +[3]: https://developer.android.google.cn/reference/kotlin/androidx/activity/result/contract/ActivityResultContracts.PickMultipleVisualMedia diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 96bf727a881..c0fe90327b9 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -404,7 +404,7 @@ private void launchTakeVideoWithCameraIntent() { } catch (ActivityNotFoundException e) { try { // If we can't delete the file again here, there's not really anything we can do about it. - //noinspection ResultOfMethodCallIgnored + // noinspection ResultOfMethodCallIgnored videoFile.delete(); } catch (SecurityException exception) { exception.printStackTrace(); @@ -515,7 +515,7 @@ private void launchTakeImageWithCameraIntent() { } catch (ActivityNotFoundException e) { try { // If we can't delete the file again here, there's not really anything we can do about it. - //noinspection ResultOfMethodCallIgnored + // noinspection ResultOfMethodCallIgnored imageFile.delete(); } catch (SecurityException exception) { exception.printStackTrace(); @@ -638,24 +638,57 @@ public boolean onActivityResult( return true; } - private void handleChooseImageResult(int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && data != null) { - Uri uri = data.getData(); - // On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could be null. - if (uri == null) { - ClipData clipData = data.getClipData(); - if (clipData != null && clipData.getItemCount() == 1) { - uri = clipData.getItemAt(0).getUri(); + @Nullable + private ArrayList getPathsFromIntent(@NonNull Intent data, boolean includeMimeType) { + ArrayList paths = new ArrayList<>(); + + Uri uri = data.getData(); + // On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could + // be null. + if (uri == null) { + ClipData clipData = data.getClipData(); + + // If data.getData() and data.getClipData() are both null, we are in an error state. By + // convention we return null from here, and then finish with an error from the corresponding + // handler. + if (clipData == null) { + return null; + } + + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + uri = data.getClipData().getItemAt(i).getUri(); + // Same error state as above. + if (uri == null) { + return null; + } + String path = fileUtils.getPathFromUri(activity, uri); + // Again, same error state as above. + if (path == null) { + return null; } + String mimeType = includeMimeType ? activity.getContentResolver().getType(uri) : null; + paths.add(new MediaPath(path, mimeType)); + } + } else { + String path = fileUtils.getPathFromUri(activity, uri); + if (path == null) { + return null; } + paths.add(new MediaPath(path, null)); + } + return paths; + } + + private void handleChooseImageResult(int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && data != null) { + ArrayList paths = getPathsFromIntent(data, false); // If there's no valid Uri, return an error - if (uri == null) { + if (paths == null) { finishWithError("no_valid_image_uri", "Cannot find the selected image."); return; } - String path = fileUtils.getPathFromUri(activity, uri); - handleImageResult(path, false); + handleMediaResult(paths); return; } @@ -683,17 +716,13 @@ public MediaPath(@NonNull String path, @Nullable String mimeType) { private void handleChooseMediaResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK && intent != null) { - ArrayList paths = new ArrayList<>(); - if (intent.getClipData() != null) { - for (int i = 0; i < intent.getClipData().getItemCount(); i++) { - Uri uri = intent.getClipData().getItemAt(i).getUri(); - String path = fileUtils.getPathFromUri(activity, uri); - String mimeType = activity.getContentResolver().getType(uri); - paths.add(new MediaPath(path, mimeType)); - } - } else { - paths.add(new MediaPath(fileUtils.getPathFromUri(activity, intent.getData()), null)); + ArrayList paths = getPathsFromIntent(intent, true); + // If there's no valid Uri, return an error + if (paths == null) { + finishWithError("no_valid_media_uri", "Cannot find the selected media."); + return; } + handleMediaResult(paths); return; } @@ -704,17 +733,14 @@ private void handleChooseMediaResult(int resultCode, Intent intent) { private void handleChooseMultiImageResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK && intent != null) { - ArrayList paths = new ArrayList<>(); - if (intent.getClipData() != null) { - for (int i = 0; i < intent.getClipData().getItemCount(); i++) { - paths.add( - new MediaPath( - fileUtils.getPathFromUri(activity, intent.getClipData().getItemAt(i).getUri()), - null)); - } - } else { - paths.add(new MediaPath(fileUtils.getPathFromUri(activity, intent.getData()), null)); + ArrayList paths = getPathsFromIntent(intent, false); + // If there's no valid Uri, return an error + if (paths == null) { + finishWithError( + "missing_valid_image_uri", "Cannot find at least one of the selected images."); + return; } + handleMediaResult(paths); return; } @@ -725,22 +751,14 @@ private void handleChooseMultiImageResult(int resultCode, Intent intent) { private void handleChooseVideoResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { - Uri uri = data.getData(); - // On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could be null. - if (uri == null) { - ClipData clipData = data.getClipData(); - if (clipData != null && clipData.getItemCount() == 1) { - uri = clipData.getItemAt(0).getUri(); - } - } + ArrayList paths = getPathsFromIntent(data, false); // If there's no valid Uri, return an error - if (uri == null) { + if (paths == null || paths.size() < 1) { finishWithError("no_valid_video_uri", "Cannot find the selected video."); return; } - String path = fileUtils.getPathFromUri(activity, uri); - handleVideoResult(path); + finishWithSuccess(paths.get(0).path); return; } @@ -771,7 +789,7 @@ private void handleCaptureVideoResult(int resultCode) { localPendingCameraMediaUrl != null ? localPendingCameraMediaUrl : Uri.parse(cache.retrievePendingCameraMediaUriPath()), - this::handleVideoResult); + this::finishWithSuccess); return; } @@ -834,10 +852,6 @@ private void handleMediaResult(@NonNull ArrayList paths) { } } - private void handleVideoResult(String path) { - finishWithSuccess(path); - } - private boolean setPendingOptionsAndResult( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 8096930e7d7..264921db4f4 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -18,7 +18,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugins.imagepicker.Messages.CacheRetrievalResult; import io.flutter.plugins.imagepicker.Messages.FlutterError; import io.flutter.plugins.imagepicker.Messages.GeneralOptions; @@ -117,7 +116,6 @@ private class ActivityState { final Activity activity, final BinaryMessenger messenger, final ImagePickerApi handler, - final PluginRegistry.Registrar registrar, final ActivityPluginBinding activityBinding) { this.application = application; this.activity = activity; @@ -127,18 +125,12 @@ private class ActivityState { delegate = constructDelegate(activity); ImagePickerApi.setUp(messenger, handler); observer = new LifeCycleObserver(activity); - if (registrar != null) { - // V1 embedding setup for activity listeners. - application.registerActivityLifecycleCallbacks(observer); - registrar.addActivityResultListener(delegate); - registrar.addRequestPermissionsResultListener(delegate); - } else { - // V2 embedding setup for activity listeners. - activityBinding.addActivityResultListener(delegate); - activityBinding.addRequestPermissionsResultListener(delegate); - lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding); - lifecycle.addObserver(observer); - } + + // V2 embedding setup for activity listeners. + activityBinding.addActivityResultListener(delegate); + activityBinding.addRequestPermissionsResultListener(delegate); + lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding); + lifecycle.addObserver(observer); } // Only invoked by {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing. @@ -183,20 +175,6 @@ ImagePickerDelegate getDelegate() { private FlutterPluginBinding pluginBinding; ActivityState activityState; - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - if (registrar.activity() == null) { - // If a background flutter view tries to register the plugin, there will be no activity from the registrar, - // we stop the registering process immediately because the ImagePicker requires an activity. - return; - } - Activity activity = registrar.activity(); - Application application = (Application) (registrar.context().getApplicationContext()); - ImagePickerPlugin plugin = new ImagePickerPlugin(); - plugin.setup(registrar.messenger(), application, activity, registrar, null); - } - /** * Default constructor for the plugin. * @@ -231,7 +209,6 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { pluginBinding.getBinaryMessenger(), (Application) pluginBinding.getApplicationContext(), binding.getActivity(), - null, binding); } @@ -254,10 +231,8 @@ private void setup( final BinaryMessenger messenger, final Application application, final Activity activity, - final PluginRegistry.Registrar registrar, final ActivityPluginBinding activityBinding) { - activityState = - new ActivityState(application, activity, messenger, this, registrar, activityBinding); + activityState = new ActivityState(application, activity, messenger, this, activityBinding); } private void tearDown() { diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 81546da52ec..bf2f4af6cb4 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -182,7 +182,6 @@ public void chooseMediaFromGallery_whenPendingResultExists_finishesWithAlreadyAc @Test @Config(sdk = 30) public void chooseImageFromGallery_launchesChooseFromGalleryIntent() { - ImagePickerDelegate delegate = createDelegate(); delegate.chooseImageFromGallery(DEFAULT_IMAGE_OPTIONS, false, mockResult); @@ -194,7 +193,6 @@ public void chooseImageFromGallery_launchesChooseFromGalleryIntent() { @Test @Config(minSdk = 33) public void chooseImageFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() { - ImagePickerDelegate delegate = createDelegate(); delegate.chooseImageFromGallery(DEFAULT_IMAGE_OPTIONS, true, mockResult); @@ -206,7 +204,6 @@ public void chooseImageFromGallery_withPhotoPicker_launchesChooseFromGalleryInte @Test @Config(sdk = 30) public void chooseMultiImageFromGallery_launchesChooseFromGalleryIntent() { - ImagePickerDelegate delegate = createDelegate(); delegate.chooseMultiImageFromGallery( DEFAULT_IMAGE_OPTIONS, true, Integer.MAX_VALUE, mockResult); @@ -220,7 +217,6 @@ public void chooseMultiImageFromGallery_launchesChooseFromGalleryIntent() { @Test @Config(minSdk = 33) public void chooseMultiImageFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() { - ImagePickerDelegate delegate = createDelegate(); delegate.chooseMultiImageFromGallery( DEFAULT_IMAGE_OPTIONS, false, Integer.MAX_VALUE, mockResult); @@ -234,7 +230,6 @@ public void chooseMultiImageFromGallery_withPhotoPicker_launchesChooseFromGaller @Test @Config(sdk = 30) public void chooseVideoFromGallery_launchesChooseFromGalleryIntent() { - ImagePickerDelegate delegate = createDelegate(); delegate.chooseVideoFromGallery(DEFAULT_VIDEO_OPTIONS, true, mockResult); @@ -246,7 +241,6 @@ public void chooseVideoFromGallery_launchesChooseFromGalleryIntent() { @Test @Config(minSdk = 33) public void chooseVideoFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() { - ImagePickerDelegate delegate = createDelegate(); delegate.chooseVideoFromGallery(DEFAULT_VIDEO_OPTIONS, true, mockResult); @@ -820,6 +814,55 @@ public void onActivityResult_withUnknownRequest_returnsFalse() { assertFalse(isHandled); } + @Test + public void onActivityResult_whenImagePickedFromGallery_finishesWithErrorIfClipDataIsNull() { + when(mockIntent.getData()).thenReturn(null); + when(mockIntent.getClipData()).thenReturn(null); + + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); + ImagePickerDelegate delegate = + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + + ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); + verify(mockResult).error(errorCaptor.capture()); + assertEquals("no_valid_media_uri", errorCaptor.getValue().code); + assertEquals("Cannot find the selected media.", errorCaptor.getValue().getMessage()); + } + + @Test + public void onActivityResult_whenImagePickedFromGallery_finishesWithErrorIfClipDataUriIsNull() { + setupMockClipDataNullUri(); + when(mockIntent.getData()).thenReturn(null); + when(mockIntent.getClipData()).thenReturn(null); + + Mockito.doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any(Runnable.class)); + ImagePickerDelegate delegate = + createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY, Activity.RESULT_OK, mockIntent); + + ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); + verify(mockResult).error(errorCaptor.capture()); + assertEquals("no_valid_media_uri", errorCaptor.getValue().code); + assertEquals("Cannot find the selected media.", errorCaptor.getValue().getMessage()); + } + private ImagePickerDelegate createDelegate() { return new ImagePickerDelegate( mockActivity, diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index cf088b59b19..dc1f9dd502f 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -78,10 +78,6 @@ public class ImagePickerPluginTest { .setCamera(Messages.SourceCamera.REAR) .build(); - @SuppressWarnings("deprecation") - @Mock - io.flutter.plugin.common.PluginRegistry.Registrar mockRegistrar; - @Mock ActivityPluginBinding mockActivityBinding; @Mock FlutterPluginBinding mockPluginBinding; @@ -97,7 +93,6 @@ public class ImagePickerPluginTest { @Before public void setUp() { mockCloseable = MockitoAnnotations.openMocks(this); - when(mockRegistrar.context()).thenReturn(mockApplication); when(mockActivityBinding.getActivity()).thenReturn(mockActivity); when(mockPluginBinding.getApplicationContext()).thenReturn(mockApplication); plugin = new ImagePickerPlugin(mockImagePickerDelegate, mockActivity); @@ -304,14 +299,6 @@ public void pickVideos_whenSourceIsCamera_invokesTakeImageWithCamera_FrontCamera verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.FRONT)); } - @Test - public void onRegister_whenActivityIsNull_shouldNotCrash() { - when(mockRegistrar.activity()).thenReturn(null); - ImagePickerPlugin.registerWith((mockRegistrar)); - assertTrue( - "No exception thrown when ImagePickerPlugin.registerWith ran with activity = null", true); - } - @Test public void onConstructor_whenContextTypeIsActivity_shouldNotCrash() { new ImagePickerPlugin(mockImagePickerDelegate, mockActivity); diff --git a/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/image_picker/image_picker_android/example/android/build.gradle b/packages/image_picker/image_picker_android/example/android/build.gradle index 381a331887a..facf61d83e6 100755 --- a/packages/image_picker/image_picker_android/example/android/build.gradle +++ b/packages/image_picker/image_picker_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/image_picker/image_picker_android/example/android/settings.gradle b/packages/image_picker/image_picker_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100755 --- a/packages/image_picker/image_picker_android/example/android/settings.gradle +++ b/packages/image_picker/image_picker_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml index 500b47c3578..0c7181f68ab 100644 --- a/packages/image_picker/image_picker_android/example/pubspec.yaml +++ b/packages/image_picker/image_picker_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the image_picker plugin. publish_to: none environment: - sdk: ^3.3.0 - flutter: ">=3.19.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 8f0606a558e..1bb91c514db 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,11 +2,11 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.10 +version: 0.8.12+3 environment: - sdk: ^3.3.0 - flutter: ">=3.19.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_for_web/README.md b/packages/image_picker/image_picker_for_web/README.md index 9e581d550dd..0e82d9887f2 100644 --- a/packages/image_picker/image_picker_for_web/README.md +++ b/packages/image_picker/image_picker_for_web/README.md @@ -57,7 +57,7 @@ The argument `maxDuration` is not supported on the web. ### Import the package -This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +This package is [endorsed](https://flutter.dev/to/endorsed-federated-plugin), which means you can simply use `image_picker` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. diff --git a/packages/image_picker/image_picker_for_web/example/README.md b/packages/image_picker/image_picker_for_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/image_picker/image_picker_for_web/example/README.md +++ b/packages/image_picker/image_picker_for_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 787524c0e55..4bbdbe6e865 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.8.12 + +* Re-adds Swift Package Manager compatibility. + +## 0.8.11+2 + +* Temporarily remove Swift Package Manager compatibility to resolve issues with Cocoapods builds. + +## 0.8.11+1 + +* Makes all headers public with Swift Package Manager integration to keep inline with CocoaPods. + +## 0.8.11 + +* Adds Swift Package Manager compatibility. + +## 0.8.10+1 + +* Fixes a possible crash when calling a picker method UIGraphicsImageRenderer if imageToScale is nil. + ## 0.8.10 * Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` that sets a limit to how many media or image items can be selected. diff --git a/packages/image_picker/image_picker_ios/README.md b/packages/image_picker/image_picker_ios/README.md index 7375ecec55d..02f126bbcf3 100755 --- a/packages/image_picker/image_picker_ios/README.md +++ b/packages/image_picker/image_picker_ios/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/image_picker -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/image_picker/image_picker_ios/example/ios/Podfile b/packages/image_picker/image_picker_ios/example/ios/Podfile index c5bd89706bc..4ea56c9ad53 100644 --- a/packages/image_picker/image_picker_ios/example/ios/Podfile +++ b/packages/image_picker/image_picker_ios/example/ios/Podfile @@ -33,8 +33,6 @@ target 'Runner' do target 'RunnerTests' do platform :ios, '12.0' inherit! :search_paths - # Pods for testing - pod 'OCMock', '~> 3.8.1' end end diff --git a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj index 82e6e009cb0..8e004beddfa 100644 --- a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 7865C5FD294157BC0010E17F /* icnsImage.icns in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FB294157BB0010E17F /* icnsImage.icns */; }; 7865C5FF294252A60010E17F /* proRawImage.dng in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FE294252A60010E17F /* proRawImage.dng */; }; 7865C600294252A60010E17F /* proRawImage.dng in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FE294252A60010E17F /* proRawImage.dng */; }; + 78CF8D862BC5E7070051231B /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = 78CF8D852BC5E7070051231B /* OCMock */; }; 86430DF9272D71E9002D9D6C /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; 86E9A893272754860017E6E0 /* PickerSaveImageToPathOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */; }; 86E9A894272754A30017E6E0 /* webpImage.webp in Resources */ = {isa = PBXBuildFile; fileRef = 86E9A88F272747B90017E6E0 /* webpImage.webp */; }; @@ -129,6 +130,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78CF8D862BC5E7070051231B /* OCMock in Frameworks */, 3A72BAD3FAE6E0FA9D80826B /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -291,6 +293,9 @@ 334733F82668136400DCC49E /* PBXTargetDependency */, ); name = RunnerTests; + packageProductDependencies = ( + 78CF8D852BC5E7070051231B /* OCMock */, + ); productName = RunnerTests; productReference = 334733F22668136400DCC49E /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -342,7 +347,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 334733F12668136400DCC49E = { @@ -374,6 +379,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 78CF8D842BC5E7070051231B /* XCRemoteSwiftPackageReference "ocmock" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -879,6 +887,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 78CF8D842BC5E7070051231B /* XCRemoteSwiftPackageReference "ocmock" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/erikdoe/ocmock"; + requirement = { + kind = revision; + revision = ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78CF8D852BC5E7070051231B /* OCMock */ = { + isa = XCSwiftPackageProductDependency; + package = 78CF8D842BC5E7070051231B /* XCRemoteSwiftPackageReference "ocmock" */; + productName = OCMock; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..69f8d7acc40 --- /dev/null +++ b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,13 @@ +{ + "pins" : [ + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a" + } + } + ], + "version" : 2 +} diff --git a/packages/image_picker/image_picker_ios/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/image_picker/image_picker_ios/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..69f8d7acc40 --- /dev/null +++ b/packages/image_picker/image_picker_ios/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,13 @@ +{ + "pins" : [ + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a" + } + } + ], + "version" : 2 +} 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 d54d5c3ec24..3ade851e1ec 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 @@ -5,7 +5,9 @@ #import "ImagePickerTestImages.h" @import image_picker_ios; +#if __has_include() @import image_picker_ios.Test; +#endif @import UniformTypeIdentifiers; @import XCTest; diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m index 5566e1d9222..6be8f28bd3d 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m @@ -5,7 +5,9 @@ #import "ImagePickerTestImages.h" @import image_picker_ios; +#if __has_include() @import image_picker_ios.Test; +#endif @import XCTest; // Corner colors of test image scaled to 3x2. Format is "R G B A". @@ -217,4 +219,24 @@ - (void)testScaledImage_WideImage_ShouldNotBeScaledAboveOriginaWidthOrHeight { XCTAssertEqual(newImage.size.height, 7); } +- (void)testScaledImage_ImageIsNil { + UIImage *image = nil; + UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image + maxWidth:@1440 + maxHeight:@1440 + isMetadataAvailable:YES]; + + XCTAssertEqual(newImage, nil); +} + +- (void)testScaledImage_ImageMaxWidthZeroAndMaxHeightIsZero { + UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; + UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image + maxWidth:@0 + maxHeight:@0 + isMetadataAvailable:YES]; + + XCTAssertEqual(newImage, nil); +} + @end diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/MetaDataUtilTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/MetaDataUtilTests.m index b684a214570..2ae51cdb93f 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/MetaDataUtilTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/MetaDataUtilTests.m @@ -5,7 +5,9 @@ #import "ImagePickerTestImages.h" @import image_picker_ios; +#if __has_include() @import image_picker_ios.Test; +#endif @import XCTest; @interface MetaDataUtilTests : XCTestCase diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m index fd8a2fcc810..45ad94a00e0 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m @@ -5,7 +5,9 @@ #import "ImagePickerTestImages.h" @import image_picker_ios; +#if __has_include() @import image_picker_ios.Test; +#endif @import XCTest; @interface PhotoAssetUtilTests : XCTestCase diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m index ad0a4c4b702..b9c97a645fb 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m @@ -5,7 +5,9 @@ #import @import image_picker_ios; +#if __has_include() @import image_picker_ios.Test; +#endif @import UniformTypeIdentifiers; @import XCTest; diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios.podspec b/packages/image_picker/image_picker_ios/ios/image_picker_ios.podspec index 4e475383520..17ef1b96826 100644 --- a/packages/image_picker/image_picker_ios/ios/image_picker_ios.podspec +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios.podspec @@ -14,11 +14,11 @@ Downloaded by pub (not CocoaPods). s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/image_picker_ios' } s.documentation_url = 'https://pub.dev/packages/image_picker_ios' - s.source_files = 'Classes/**/*.{h,m}' - s.public_header_files = 'Classes/**/*.h' - s.module_map = 'Classes/ImagePickerPlugin.modulemap' + s.source_files = 'image_picker_ios/Sources/image_picker_ios/**/*.{h,m}' + s.public_header_files = 'image_picker_ios/Sources/image_picker_ios/**/*.h' + s.module_map = 'image_picker_ios/Sources/image_picker_ios/include/ImagePickerPlugin.modulemap' s.dependency 'Flutter' s.platform = :ios, '12.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.resource_bundles = {'image_picker_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'image_picker_ios_privacy' => ['image_picker_ios/Sources/image_picker_ios/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Package.swift b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Package.swift new file mode 100644 index 00000000000..987b206fb2e --- /dev/null +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 5.9 + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import PackageDescription + +let package = Package( + name: "image_picker_ios", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "image-picker-ios", targets: ["image_picker_ios"]) + ], + dependencies: [], + targets: [ + .target( + name: "image_picker_ios", + dependencies: [], + exclude: ["include/image_picker_ios-umbrella.h", "include/ImagePickerPlugin.modulemap"], + resources: [ + .process("Resources") + ], + cSettings: [ + .headerSearchPath("include/image_picker_ios") + ] + ) + ] +) diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerImageUtil.m similarity index 98% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerImageUtil.m index 547632871d1..d71175da1e9 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerImageUtil.m @@ -29,6 +29,9 @@ - (instancetype)initWithImages:(NSArray *)images interval:(NSTimeInte @implementation FLTImagePickerImageUtil : NSObject static UIImage *FLTImagePickerDrawScaledImage(UIImage *imageToScale, double width, double height) { + if (imageToScale == nil || width == 0 || height == 0) { + return nil; + } UIGraphicsImageRenderer *imageRenderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(width, height) format:imageToScale.imageRendererFormat]; diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerMetaDataUtil.m similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerMetaDataUtil.m diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPhotoAssetUtil.m similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPhotoAssetUtil.m diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m similarity index 99% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m index c65542ddf12..f54db3b9fef 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m @@ -12,11 +12,11 @@ #import #import +#import "./include/image_picker_ios/messages.g.h" #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" #import "FLTPHPickerSaveImageToPathOperation.h" -#import "messages.g.h" @implementation FLTImagePickerMethodCallContext - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result { diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTPHPickerSaveImageToPathOperation.m similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTPHPickerSaveImageToPathOperation.m diff --git a/packages/image_picker/image_picker_ios/ios/Resources/PrivacyInfo.xcprivacy b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Resources/PrivacyInfo.xcprivacy rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/Resources/PrivacyInfo.xcprivacy diff --git a/packages/image_picker/image_picker_ios/ios/Classes/ImagePickerPlugin.modulemap b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/ImagePickerPlugin.modulemap similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/ImagePickerPlugin.modulemap rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/ImagePickerPlugin.modulemap diff --git a/packages/image_picker/image_picker_ios/ios/Classes/image_picker_ios-umbrella.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios-umbrella.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/image_picker_ios-umbrella.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios-umbrella.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerImageUtil.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerImageUtil.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerMetaDataUtil.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerMetaDataUtil.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPhotoAssetUtil.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPhotoAssetUtil.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPlugin.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPlugin.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPlugin_Test.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPlugin_Test.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTPHPickerSaveImageToPathOperation.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTPHPickerSaveImageToPathOperation.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/messages.g.h similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Classes/messages.g.h rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/messages.g.h diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/messages.g.m similarity index 99% rename from packages/image_picker/image_picker_ios/ios/Classes/messages.g.m rename to packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/messages.g.m index a1f5794dd6a..0588aa93266 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/messages.g.m @@ -4,7 +4,7 @@ // Autogenerated from Pigeon (v17.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon -#import "messages.g.h" +#import "./include/image_picker_ios/messages.g.h" #if TARGET_OS_OSX #import diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index d8ae8954e98..4a31ae0b6c3 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -7,10 +7,12 @@ 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: + 'ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/messages.g.h', + objcSourceOut: 'ios/image_picker_ios/Sources/image_picker_ios/messages.g.m', objcOptions: ObjcOptions( prefix: 'FLT', + headerIncludePath: './include/image_picker_ios/messages.g.h', ), copyrightHeader: 'pigeons/copyright.txt', )) diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 70d578be360..bbc99bc79f3 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.10 +version: 0.8.12 environment: sdk: ^3.3.0 diff --git a/packages/image_picker/image_picker_linux/CHANGELOG.md b/packages/image_picker/image_picker_linux/CHANGELOG.md index 3fc7283141e..0299d9f5257 100644 --- a/packages/image_picker/image_picker_linux/CHANGELOG.md +++ b/packages/image_picker/image_picker_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.2.1+1 diff --git a/packages/image_picker/image_picker_linux/README.md b/packages/image_picker/image_picker_linux/README.md index 1f1833e81e6..55b58356c70 100644 --- a/packages/image_picker/image_picker_linux/README.md +++ b/packages/image_picker/image_picker_linux/README.md @@ -24,4 +24,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/image_picker -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/image_picker/image_picker_linux/example/pubspec.yaml b/packages/image_picker/image_picker_linux/example/pubspec.yaml index a49e487d243..f211797e1c3 100644 --- a/packages/image_picker/image_picker_linux/example/pubspec.yaml +++ b/packages/image_picker/image_picker_linux/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_linux/pubspec.yaml b/packages/image_picker/image_picker_linux/pubspec.yaml index 5be7f927eec..c0d530e5b9e 100644 --- a/packages/image_picker/image_picker_linux/pubspec.yaml +++ b/packages/image_picker/image_picker_linux/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.2.1+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_macos/CHANGELOG.md b/packages/image_picker/image_picker_macos/CHANGELOG.md index 2213848edf9..16afc6978af 100644 --- a/packages/image_picker/image_picker_macos/CHANGELOG.md +++ b/packages/image_picker/image_picker_macos/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.2.1+1 diff --git a/packages/image_picker/image_picker_macos/README.md b/packages/image_picker/image_picker_macos/README.md index ec76d85e26b..9aa87453532 100644 --- a/packages/image_picker/image_picker_macos/README.md +++ b/packages/image_picker/image_picker_macos/README.md @@ -33,6 +33,6 @@ need to add a read-only file acces [entitlement][4]: ``` [1]: https://pub.dev/packages/image_picker -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://pub.dev/packages/file_selector -[4]: https://docs.flutter.dev/platform-integration/macos/building#entitlements-and-the-app-sandbox +[4]: https://flutter.dev/to/macos-entitlements diff --git a/packages/image_picker/image_picker_macos/example/pubspec.yaml b/packages/image_picker/image_picker_macos/example/pubspec.yaml index cff5501cfa2..e6f1b3ba1eb 100644 --- a/packages/image_picker/image_picker_macos/example/pubspec.yaml +++ b/packages/image_picker/image_picker_macos/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_macos/pubspec.yaml b/packages/image_picker/image_picker_macos/pubspec.yaml index 5fc9563c02b..ce1d4ae5b99 100644 --- a/packages/image_picker/image_picker_macos/pubspec.yaml +++ b/packages/image_picker/image_picker_macos/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.2.1+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_windows/CHANGELOG.md b/packages/image_picker/image_picker_windows/CHANGELOG.md index 8575f8064c3..eb9eed2363e 100644 --- a/packages/image_picker/image_picker_windows/CHANGELOG.md +++ b/packages/image_picker/image_picker_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.2.1+1 diff --git a/packages/image_picker/image_picker_windows/README.md b/packages/image_picker/image_picker_windows/README.md index 1aa30b17fc0..d071b2a6f43 100644 --- a/packages/image_picker/image_picker_windows/README.md +++ b/packages/image_picker/image_picker_windows/README.md @@ -24,4 +24,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/image_picker -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/image_picker/image_picker_windows/example/pubspec.yaml b/packages/image_picker/image_picker_windows/example/pubspec.yaml index 983f9520e36..0d50b6751b0 100644 --- a/packages/image_picker/image_picker_windows/example/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_windows/pubspec.yaml b/packages/image_picker/image_picker_windows/pubspec.yaml index 23e4777888f..ca65e3dcfb6 100644 --- a/packages/image_picker/image_picker_windows/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.2.1+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 2cd388006e8..fc0f7d286cb 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 3.2.0 * Adds `countryCode` API. diff --git a/packages/in_app_purchase/in_app_purchase/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/in_app_purchase/in_app_purchase/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/in_app_purchase/in_app_purchase/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/in_app_purchase/in_app_purchase/example/android/build.gradle b/packages/in_app_purchase/in_app_purchase/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/in_app_purchase/in_app_purchase/example/android/settings.gradle b/packages/in_app_purchase/in_app_purchase/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/settings.gradle +++ b/packages/in_app_purchase/in_app_purchase/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/in_app_purchase/in_app_purchase/example/macos/Runner/DebugProfile.entitlements b/packages/in_app_purchase/in_app_purchase/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/in_app_purchase/in_app_purchase/example/macos/Runner/DebugProfile.entitlements +++ b/packages/in_app_purchase/in_app_purchase/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/in_app_purchase/in_app_purchase/example/macos/Runner/Release.entitlements b/packages/in_app_purchase/in_app_purchase/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/in_app_purchase/in_app_purchase/example/macos/Runner/Release.entitlements +++ b/packages/in_app_purchase/in_app_purchase/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml index 19cd0fd9ffe..d689dce3c08 100644 --- a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the in_app_purchase plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 20d700ac410..ea9332cbe96 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,6 +1,32 @@ +## 0.3.6+1 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 0.3.6 + +* Introduces new `ReplacementMode` for Android's billing client as `ProrationMode` is being deprecated. + +## 0.3.5+2 + +* Bumps androidx.annotation:annotation from 1.7.1 to 1.8.0. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 0.3.5+1 + +* Updates example to use `countryCode` instead of deprecated `getCountryCode`. + +## 0.3.5 + +* Replaces `getCountryCode` with `countryCode`. + +## 0.3.4+1 + +* Adds documentation for UserChoice and Alternative Billing. + ## 0.3.4 -* Adds `countryCode` API. +* Adds `countryCode` API. ## 0.3.3+1 diff --git a/packages/in_app_purchase/in_app_purchase_android/README.md b/packages/in_app_purchase/in_app_purchase_android/README.md index d49315b41a0..92257cd2ba6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/README.md +++ b/packages/in_app_purchase/in_app_purchase_android/README.md @@ -11,6 +11,14 @@ so you do not need to add it to your `pubspec.yaml`. However, if you `import` this package to use any of its APIs directly, you should [add it to your `pubspec.yaml` as usual][3]. +## Alternative/UserChoice Billing + +Alternative and UserChoice billing from Google Play is exposed from this package. + +Using the Alternative billing only feature requires Google Play app configuration, checking if the feature is available (`isAlternativeBillingOnlyAvailable`) and informing users that Google Play does not handle all aspects of purchase (`showAlternativeBillingOnlyInformationDialog`). After those calls then you can call `setBillingChoice` and respond when a user attempts a purchase. + +[Google Play documentation for Alternative billing](https://developer.android.com/google/play/billing/alternative) + ## Migrating to 0.3.0 To migrate to version 0.3.0 from 0.2.x, have a look at the [migration guide](migration_guide.md). @@ -29,5 +37,5 @@ If you would like to contribute to the plugin, check out our [1]: https://pub.dev/packages/in_app_purchase -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://pub.dev/packages/in_app_purchase_android/install 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 a55e39fc886..5f0fecc642e 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 @@ -58,7 +58,7 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.7.1' + implementation 'androidx.annotation:annotation:1.8.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")) @@ -69,5 +69,5 @@ dependencies { testImplementation 'androidx.test:core:1.5.0' testImplementation 'org.robolectric:robolectric:4.10.3' androidTestImplementation 'androidx.test:runner:1.5.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java index cc153dc4309..965ed43beff 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java @@ -4,7 +4,6 @@ package io.flutter.plugins.inapppurchase; -import android.app.Application; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -26,16 +25,6 @@ public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { private MethodCallHandlerImpl methodCallHandler; - /** Plugin registration. */ - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - InAppPurchasePlugin plugin = new InAppPurchasePlugin(); - registrar.activity().getIntent().putExtra(PROXY_PACKAGE_KEY, PROXY_VALUE); - ((Application) registrar.context().getApplicationContext()) - .registerActivityLifecycleCallbacks(plugin.methodCallHandler); - } - @Override public void onAttachedToEngine(@NonNull FlutterPlugin.FlutterPluginBinding binding) { setUpMethodChannel(binding.getBinaryMessenger(), binding.getApplicationContext()); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java index 6a465b4bb2c..0602260e72f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/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 (v17.1.2), do not edit directly. +// Autogenerated from Pigeon (v17.3.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.inapppurchase; @@ -962,6 +962,19 @@ public void setProrationMode(@NonNull Long setterArg) { this.prorationMode = setterArg; } + private @NonNull Long replacementMode; + + public @NonNull Long getReplacementMode() { + return replacementMode; + } + + public void setReplacementMode(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"replacementMode\" is null."); + } + this.replacementMode = setterArg; + } + private @Nullable String offerToken; public @Nullable String getOfferToken() { @@ -1033,6 +1046,14 @@ public static final class Builder { return this; } + private @Nullable Long replacementMode; + + @CanIgnoreReturnValue + public @NonNull Builder setReplacementMode(@NonNull Long setterArg) { + this.replacementMode = setterArg; + return this; + } + private @Nullable String offerToken; @CanIgnoreReturnValue @@ -1077,6 +1098,7 @@ public static final class Builder { PlatformBillingFlowParams pigeonReturn = new PlatformBillingFlowParams(); pigeonReturn.setProduct(product); pigeonReturn.setProrationMode(prorationMode); + pigeonReturn.setReplacementMode(replacementMode); pigeonReturn.setOfferToken(offerToken); pigeonReturn.setAccountId(accountId); pigeonReturn.setObfuscatedProfileId(obfuscatedProfileId); @@ -1088,9 +1110,10 @@ public static final class Builder { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(7); + ArrayList toListResult = new ArrayList(8); toListResult.add(product); toListResult.add(prorationMode); + toListResult.add(replacementMode); toListResult.add(offerToken); toListResult.add(accountId); toListResult.add(obfuscatedProfileId); @@ -1110,15 +1133,22 @@ ArrayList toList() { : ((prorationMode instanceof Integer) ? (Integer) prorationMode : (Long) prorationMode)); - Object offerToken = list.get(2); + Object replacementMode = list.get(2); + pigeonResult.setReplacementMode( + (replacementMode == null) + ? null + : ((replacementMode instanceof Integer) + ? (Integer) replacementMode + : (Long) replacementMode)); + Object offerToken = list.get(3); pigeonResult.setOfferToken((String) offerToken); - Object accountId = list.get(3); + Object accountId = list.get(4); pigeonResult.setAccountId((String) accountId); - Object obfuscatedProfileId = list.get(4); + Object obfuscatedProfileId = list.get(5); pigeonResult.setObfuscatedProfileId((String) obfuscatedProfileId); - Object oldProduct = list.get(5); + Object oldProduct = list.get(6); pigeonResult.setOldProduct((String) oldProduct); - Object purchaseToken = list.get(6); + Object purchaseToken = list.get(7); pigeonResult.setPurchaseToken((String) purchaseToken); return pigeonResult; } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index 11ea6da2195..c7305a69938 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -60,6 +60,11 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I com.android.billingclient.api.BillingFlowParams.ProrationMode .UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; + @VisibleForTesting + static final int REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = + com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams.ReplacementMode + .UNKNOWN_REPLACEMENT_MODE; + private static final String TAG = "InAppPurchasePlugin"; private static final String LOAD_PRODUCT_DOC_URL = "https://github.com/flutter/packages/blob/main/packages/in_app_purchase/in_app_purchase/README.md#loading-products-for-sale"; @@ -285,9 +290,20 @@ public void queryProductDetailsAsync( } } + if (params.getProrationMode() != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY + && params.getReplacementMode() + != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { + throw new FlutterError( + "IN_APP_PURCHASE_CONFLICT_PRORATION_MODE_REPLACEMENT_MODE", + "launchBillingFlow failed because you provided both prorationMode and replacementMode. You can only provide one of them.", + null); + } + if (params.getOldProduct() == null - && params.getProrationMode() - != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { + && (params.getProrationMode() + != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY + || params.getReplacementMode() + != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY)) { throw new FlutterError( "IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT", "launchBillingFlow failed because oldProduct is null. You must provide a valid oldProduct in order to use a proration mode.", @@ -336,9 +352,16 @@ public void queryProductDetailsAsync( && !params.getOldProduct().isEmpty() && params.getPurchaseToken() != null) { subscriptionUpdateParamsBuilder.setOldPurchaseToken(params.getPurchaseToken()); - // Set the prorationMode using a helper to minimize impact of deprecation warning suppression. - setReplaceProrationMode( - subscriptionUpdateParamsBuilder, params.getProrationMode().intValue()); + if (params.getProrationMode() + != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { + setReplaceProrationMode( + subscriptionUpdateParamsBuilder, params.getProrationMode().intValue()); + } + if (params.getReplacementMode() + != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { + subscriptionUpdateParamsBuilder.setSubscriptionReplacementMode( + params.getReplacementMode().intValue()); + } paramsBuilder.setSubscriptionUpdateParams(subscriptionUpdateParamsBuilder.build()); } return fromBillingResult(billingClient.launchBillingFlow(activity, paramsBuilder.build())); @@ -385,7 +408,8 @@ public void queryPurchasesAsync( } try { - // Like in our connect call, consider the billing client responding a "success" here regardless + // Like in our connect call, consider the billing client responding a "success" here + // regardless // of status code. QueryPurchasesParams.Builder paramsBuilder = QueryPurchasesParams.newBuilder(); paramsBuilder.setProductType(toProductTypeString(productType)); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java index f7908c09b01..c12045b0650 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java @@ -14,7 +14,6 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -26,10 +25,6 @@ public class InAppPurchasePluginTest { static final String PROXY_PACKAGE_KEY = "PROXY_PACKAGE"; - @SuppressWarnings("deprecation") - @Mock - PluginRegistry.Registrar mockRegistrar; // For v1 embedding - @Mock Activity activity; @Mock Context context; @Mock BinaryMessenger mockMessenger; @@ -43,9 +38,6 @@ public class InAppPurchasePluginTest { @Before public void setUp() { mockCloseable = MockitoAnnotations.openMocks(this); - when(mockRegistrar.activity()).thenReturn(activity); - when(mockRegistrar.messenger()).thenReturn(mockMessenger); - when(mockRegistrar.context()).thenReturn(context); when(activity.getIntent()).thenReturn(mockIntent); when(activityPluginBinding.getActivity()).thenReturn(activity); when(flutterPluginBinding.getBinaryMessenger()).thenReturn(mockMessenger); @@ -57,27 +49,6 @@ public void tearDown() throws Exception { mockCloseable.close(); } - @Test - public void registerWith_doNotCrashWhenRegisterContextIsActivity_V1Embedding() { - when(mockRegistrar.context()).thenReturn(activity); - when(activity.getApplicationContext()).thenReturn(mockApplication); - InAppPurchasePlugin.registerWith(mockRegistrar); - } - - // The PROXY_PACKAGE_KEY value of this test (io.flutter.plugins.inapppurchase) should never be changed. - // In case there's a strong reason to change it, please inform the current code owner of the plugin. - @Test - public void registerWith_proxyIsSet_V1Embedding() { - when(mockRegistrar.context()).thenReturn(activity); - when(activity.getApplicationContext()).thenReturn(mockApplication); - InAppPurchasePlugin.registerWith(mockRegistrar); - // The `PROXY_PACKAGE_KEY` value is hard coded in the plugin code as "io.flutter.plugins.inapppurchase". - // We cannot use `BuildConfig.LIBRARY_PACKAGE_NAME` directly in the plugin code because whether to read BuildConfig.APPLICATION_ID or LIBRARY_PACKAGE_NAME - // depends on the "APP's" Android Gradle plugin version. Newer versions of AGP use LIBRARY_PACKAGE_NAME, whereas older ones use BuildConfig.APPLICATION_ID. - Mockito.verify(mockIntent).putExtra(PROXY_PACKAGE_KEY, "io.flutter.plugins.inapppurchase"); - assertEquals("io.flutter.plugins.inapppurchase", BuildConfig.LIBRARY_PACKAGE_NAME); - } - // The PROXY_PACKAGE_KEY value of this test (io.flutter.plugins.inapppurchase) should never be changed. // In case there's a strong reason to change it, please inform the current code owner of the plugin. @Test diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index ca428910868..58b1c42a0e4 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -6,6 +6,7 @@ import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.ACTIVITY_UNAVAILABLE; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; @@ -556,6 +557,8 @@ public void launchBillingFlow_null_AccountId_do_not_crash() { paramsBuilder.setProduct(productId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -581,6 +584,8 @@ public void launchBillingFlow_ok_null_OldProduct() { paramsBuilder.setAccountId(accountId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -610,6 +615,8 @@ public void launchBillingFlow_ok_null_Activity() { paramsBuilder.setAccountId(accountId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -633,6 +640,8 @@ public void launchBillingFlow_ok_oldProduct() { paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -660,6 +669,8 @@ public void launchBillingFlow_ok_AccountId() { paramsBuilder.setAccountId(accountId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -695,6 +706,8 @@ public void launchBillingFlow_ok_Proration() { paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setPurchaseToken(purchaseToken); paramsBuilder.setProrationMode((long) prorationMode); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -728,6 +741,8 @@ public void launchBillingFlow_ok_Proration_with_null_OldProduct() { paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(null); paramsBuilder.setProrationMode((long) prorationMode); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -744,6 +759,73 @@ public void launchBillingFlow_ok_Proration_with_null_OldProduct() { .contains("launchBillingFlow failed because oldProduct is null")); } + @Test + @SuppressWarnings(value = "deprecation") + public void launchBillingFlow_ok_Replacement_with_null_OldProduct() { + // Fetch the product details first and query the method call + String productId = "foo"; + String accountId = "account"; + String queryOldProductId = "oldFoo"; + int replacementMode = + BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; + queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); + PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); + paramsBuilder.setProduct(productId); + paramsBuilder.setAccountId(accountId); + paramsBuilder.setOldProduct(null); + paramsBuilder.setProrationMode( + (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode((long) replacementMode); + + // Launch the billing flow + BillingResult billingResult = buildBillingResult(); + when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); + + // Assert that the synchronous call throws an exception. + FlutterError exception = + assertThrows( + FlutterError.class, + () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); + assertEquals("IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT", exception.code); + assertTrue( + Objects.requireNonNull(exception.getMessage()) + .contains("launchBillingFlow failed because oldProduct is null")); + } + + @Test + @SuppressWarnings(value = "deprecation") + public void launchBillingFlow_ok_Proration_and_Replacement_conflict() { + // Fetch the product details first and query the method call + String productId = "foo"; + String accountId = "account"; + String queryOldProductId = "oldFoo"; + int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; + int replacementMode = + BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; + queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); + PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); + paramsBuilder.setProduct(productId); + paramsBuilder.setAccountId(accountId); + paramsBuilder.setOldProduct(queryOldProductId); + paramsBuilder.setProrationMode((long) prorationMode); + paramsBuilder.setReplacementMode((long) replacementMode); + + // Launch the billing flow + BillingResult billingResult = buildBillingResult(); + when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); + + // Assert that the synchronous call throws an exception. + FlutterError exception = + assertThrows( + FlutterError.class, + () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); + assertEquals("IN_APP_PURCHASE_CONFLICT_PRORATION_MODE_REPLACEMENT_MODE", exception.code); + assertTrue( + Objects.requireNonNull(exception.getMessage()) + .contains( + "launchBillingFlow failed because you provided both prorationMode and replacementMode. You can only provide one of them.")); + } + // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new // ReplacementMode enum values. // https://github.com/flutter/flutter/issues/128957. @@ -763,6 +845,8 @@ public void launchBillingFlow_ok_Full() { paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setPurchaseToken(purchaseToken); paramsBuilder.setProrationMode((long) prorationMode); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -790,6 +874,8 @@ public void launchBillingFlow_clientDisconnected() { paramsBuilder.setAccountId(accountId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -811,6 +897,8 @@ public void launchBillingFlow_productNotFound() { paramsBuilder.setAccountId(accountId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -835,6 +923,8 @@ public void launchBillingFlow_oldProductNotFound() { paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setProrationMode( (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode( + (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle index 6f7af720610..bde7e79aa94 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index 89883a1564a..4773446cdee 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -259,10 +259,7 @@ class _MyAppState extends State<_MyApp> { foregroundColor: Colors.white, ), onPressed: () { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - unawaited(deliverCountryCode(addition.getCountryCode())); + unawaited(deliverCountryCode(_inAppPurchasePlatform.countryCode())); }, child: const Text('Fetch Country Code'), ), diff --git a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml index 9239f83a0a3..a22c81be11f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the in_app_purchase_android plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: 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 b9417c6b25e..3f7bd499bbf 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 @@ -190,7 +190,8 @@ class BillingClient { String? obfuscatedProfileId, String? oldProduct, String? purchaseToken, - ProrationMode? prorationMode}) async { + ProrationMode? prorationMode, + ReplacementMode? replacementMode}) async { assert((oldProduct == null) == (purchaseToken == null), 'oldProduct and purchaseToken must both be set, or both be null.'); return resultWrapperFromPlatform( @@ -198,6 +199,8 @@ class BillingClient { product: product, prorationMode: const ProrationModeConverter().toJson(prorationMode ?? ProrationMode.unknownSubscriptionUpgradeDowngradePolicy), + replacementMode: const ReplacementModeConverter() + .toJson(replacementMode ?? ReplacementMode.unknownReplacementMode), offerToken: offerToken, accountId: accountId, obfuscatedProfileId: obfuscatedProfileId, @@ -622,6 +625,76 @@ class ProrationModeConverter implements JsonConverter { int toJson(ProrationMode object) => _$ProrationModeEnumMap[object]!; } +/// Enum representing the replacement mode. +/// +/// When upgrading or downgrading a subscription, set this mode to provide details +/// about the replacement that will be applied when the subscription changes. +/// +/// Wraps [`BillingFlowParams.SubscriptionUpdateParams.ReplacementMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode) +/// See the linked documentation for an explanation of the different constants. +@JsonEnum(alwaysCreate: true) +enum ReplacementMode { +// WARNING: Changes to this class need to be reflected in our generated code. +// Run `flutter packages pub run build_runner watch` to rebuild and watch for +// further changes. + + /// Unknown upgrade or downgrade policy. + @JsonValue(0) + unknownReplacementMode, + + /// Replacement takes effect immediately, and the remaining time will be prorated + /// and credited to the user. + /// + /// This is the current default behavior. + @JsonValue(1) + withTimeProration, + + /// Replacement takes effect immediately, and the billing cycle remains the same. + /// + /// The price for the remaining period will be charged. + /// This option is only available for subscription upgrade. + @JsonValue(2) + chargeProratedPrice, + + /// Replacement takes effect immediately, and the new price will be charged on next + /// recurrence time. + /// + /// The billing cycle stays the same. + @JsonValue(3) + withoutProration, + + /// Replacement takes effect when the old plan expires, and the new price will + /// be charged at the same time. + @JsonValue(6) + deferred, + + /// Replacement takes effect immediately, and the user is charged full price + /// of new plan and is given a full billing cycle of subscription, plus + /// remaining prorated time from the old plan. + @JsonValue(5) + chargeFullPrice, +} + +/// Serializer for [ReplacementMode]. +/// +/// Use these in `@JsonSerializable()` classes by annotating them with +/// `@ReplacementModeConverter()`. +class ReplacementModeConverter implements JsonConverter { + /// Default const constructor. + const ReplacementModeConverter(); + + @override + ReplacementMode fromJson(int? json) { + if (json == null) { + return ReplacementMode.unknownReplacementMode; + } + return $enumDecode(_$ReplacementModeEnumMap, json); + } + + @override + int toJson(ReplacementMode object) => _$ReplacementModeEnumMap[object]!; +} + /// Features/capabilities supported by [BillingClient.isFeatureSupported()](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType). @JsonEnum(alwaysCreate: true) enum BillingClientFeature { 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 0768c35deec..eb0033193d9 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 @@ -42,6 +42,15 @@ const _$ProrationModeEnumMap = { ProrationMode.immediateAndChargeFullPrice: 5, }; +const _$ReplacementModeEnumMap = { + ReplacementMode.unknownReplacementMode: 0, + ReplacementMode.withTimeProration: 1, + ReplacementMode.chargeProratedPrice: 2, + ReplacementMode.withoutProration: 3, + ReplacementMode.deferred: 6, + ReplacementMode.chargeFullPrice: 5, +}; + const _$BillingClientFeatureEnumMap = { BillingClientFeature.inAppItemsOnVR: 'inAppItemsOnVr', BillingClientFeature.priceChangeConfirmation: 'priceChangeConfirmation', diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 9b9e2f1b1ea..333ccc83bc2 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -162,7 +162,8 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { oldProduct: changeSubscriptionParam?.oldPurchaseDetails.productID, purchaseToken: changeSubscriptionParam ?.oldPurchaseDetails.verificationData.serverVerificationData, - prorationMode: changeSubscriptionParam?.prorationMode), + prorationMode: changeSubscriptionParam?.prorationMode, + replacementMode: changeSubscriptionParam?.replacementMode), ); return billingResultWrapper.responseCode == BillingResponse.ok; } @@ -317,9 +318,14 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { /// /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html - Future getCountryCode() async { + @override + Future countryCode() async { final BillingConfigWrapper billingConfig = await billingClientManager .runWithClient((BillingClient client) => client.getBillingConfig()); return billingConfig.countryCode; } + + /// Use countryCode instead. + @Deprecated('Use countryCode') + Future getCountryCode() => countryCode(); } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index c0ca171ad6d..aa2f473d263 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -167,7 +167,7 @@ class InAppPurchaseAndroidPlatformAddition /// /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html - @Deprecated('Use InAppPurchasePlatfrom.getCountryCode') + @Deprecated('Use InAppPurchasePlatfrom.countryCode') Future getCountryCode() async { final BillingConfigWrapper billingConfig = await _billingClientManager .runWithClient((BillingClient client) => client.getBillingConfig()); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart index 1b5753f8006..e6444b8d9b6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_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 (v17.1.2), do not edit directly. +// Autogenerated from Pigeon (v17.3.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, no_leading_underscores_for_local_identifiers @@ -324,6 +324,7 @@ class PlatformBillingFlowParams { PlatformBillingFlowParams({ required this.product, required this.prorationMode, + required this.replacementMode, this.offerToken, this.accountId, this.obfuscatedProfileId, @@ -335,6 +336,8 @@ class PlatformBillingFlowParams { int prorationMode; + int replacementMode; + String? offerToken; String? accountId; @@ -349,6 +352,7 @@ class PlatformBillingFlowParams { return [ product, prorationMode, + replacementMode, offerToken, accountId, obfuscatedProfileId, @@ -362,11 +366,12 @@ class PlatformBillingFlowParams { return PlatformBillingFlowParams( product: result[0]! as String, prorationMode: result[1]! as int, - offerToken: result[2] as String?, - accountId: result[3] as String?, - obfuscatedProfileId: result[4] as String?, - oldProduct: result[5] as String?, - purchaseToken: result[6] as String?, + replacementMode: result[2]! as int, + offerToken: result[3] as String?, + accountId: result[4] as String?, + obfuscatedProfileId: result[5] as String?, + oldProduct: result[6] as String?, + purchaseToken: result[7] as String?, ); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart index 1099da3bf15..f76fc3e9005 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart @@ -10,7 +10,8 @@ class ChangeSubscriptionParam { /// Creates a new change subscription param object with given data ChangeSubscriptionParam({ required this.oldPurchaseDetails, - this.prorationMode, + @Deprecated('Use replacementMode instead') this.prorationMode, + this.replacementMode, }); /// The purchase object of the existing subscription that the user needs to @@ -21,5 +22,12 @@ class ChangeSubscriptionParam { /// /// This is an optional parameter that indicates how to handle the existing /// subscription when the new subscription comes into effect. + @Deprecated('Use replacementMode instead') final ProrationMode? prorationMode; + + /// The replacement mode. + /// + /// This is an optional parameter that indicates how to handle the existing + /// subscription when the new subscription comes into effect. + final ReplacementMode? replacementMode; } diff --git a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart index 2921c0907ca..d0bbc51e973 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart @@ -118,6 +118,7 @@ class PlatformBillingFlowParams { PlatformBillingFlowParams({ required this.product, required this.prorationMode, + required this.replacementMode, required this.offerToken, required this.accountId, required this.obfuscatedProfileId, @@ -130,6 +131,7 @@ class PlatformBillingFlowParams { // to constants on the Java side, but it's deprecated anyway so that will be // resolved during the update to the new API. final int prorationMode; + final int replacementMode; final String? offerToken; final String? accountId; final String? obfuscatedProfileId; 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 6950533f1ca..29961fcfe4c 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,12 +2,11 @@ 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.4 +version: 0.3.6+1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index c5e5d628ef8..ee183eeba71 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -760,9 +760,12 @@ void main() { when(mockApi.getBillingConfigAsync()) .thenAnswer((_) async => platformBillingConfigFromWrapper(expected)); - final String countryCode = await iapAndroidPlatform.getCountryCode(); + final String countryCode = await iapAndroidPlatform.countryCode(); expect(countryCode, equals(expectedCountryCode)); + // Ensure deprecated code keeps working until removed. + expect(await iapAndroidPlatform.getCountryCode(), + equals(expectedCountryCode)); }); }); } diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md index 11d14d945e7..05c0849f416 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 1.4.0 * Adds `getCountryCode` API. diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml index 35fc0488d9d..41aa8644613 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.4.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 114dc9e8df5..18072ee2a3e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.3.17 + +* Removes OCMock from tests. + +## 0.3.16 + +* Converts main plugin class to Swift. + +## 0.3.15 + +* Replaces `getCountryCode` with `countryCode`. + ## 0.3.14 * Adds `countryCode` API. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/README.md b/packages/in_app_purchase/in_app_purchase_storekit/README.md index 779d81fc3cf..3c7b5fac08d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/README.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/README.md @@ -27,5 +27,5 @@ If you would like to contribute to the plugin, check out our [1]: ../in_app_purchase -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://pub.dev/packages/in_app_purchase_storekit/install diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h index 4347846f54c..12ef96bee5c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h @@ -9,13 +9,14 @@ #endif #import #import +#import "FLTMethodChannelProtocol.h" NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(ios(13)) API_UNAVAILABLE(tvos, macos, watchos) @interface FIAPPaymentQueueDelegate : NSObject -- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel; +- (id)initWithMethodChannel:(id)methodChannel; @end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m index cb18d9b86d6..d0efb06861a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m @@ -7,13 +7,14 @@ @interface FIAPPaymentQueueDelegate () -@property(strong, nonatomic, readonly) FlutterMethodChannel *callbackChannel; +// The designated Flutter method channel that handles if a transaction should be continued +@property(nonatomic, strong, readonly) id callbackChannel; @end @implementation FIAPPaymentQueueDelegate -- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel { +- (id)initWithMethodChannel:(id)methodChannel { self = [super init]; if (self) { _callbackChannel = methodChannel; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m index 22a3973b7fc..503eb0fcf3e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m @@ -14,13 +14,15 @@ @interface FIAPReceiptManager () // Gets the receipt file data from the location of the url. Can be nil if // there is an error. This interface is defined so it can be stubbed for testing. - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error; - +// Gets the app store receipt url. Can be nil if +// there is an error. This property is defined so it can be stubbed for testing. +@property(nonatomic, readonly) NSURL *receiptURL; @end @implementation FIAPReceiptManager - (NSString *)retrieveReceiptWithError:(FlutterError **)flutterError { - NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + NSURL *receiptURL = self.receiptURL; if (!receiptURL) { return nil; } @@ -43,4 +45,8 @@ - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error { return [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:error]; } +- (NSURL *)receiptURL { + return [[NSBundle mainBundle] appStoreReceiptURL]; +} + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h index cbf21d6e161..ea6e2b736af 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h @@ -2,19 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import #import #import NS_ASSUME_NONNULL_BEGIN -typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response, - NSError *_Nullable errror); - -@interface FIAPRequestHandler : NSObject +@interface FIAPRequestHandler : NSObject - (instancetype)initWithRequest:(SKRequest *)request; - (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion; @end +// The default request handler that wraps FIAPRequestHandler +@interface DefaultRequestHandler : NSObject + +// Initialize this wrapper with an instance of FIAPRequestHandler +- (instancetype)initWithRequestHandler:(FIAPRequestHandler *)handler; +@end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m index 8767265d854..d2ef6829eec 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m @@ -9,8 +9,8 @@ @interface FIAPRequestHandler () -@property(copy, nonatomic) ProductRequestCompletion completion; -@property(strong, nonatomic) SKRequest *request; +@property(nonatomic, copy) ProductRequestCompletion completion; +@property(nonatomic, strong) SKRequest *request; @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h index 356940cd39b..68ddcaa7651 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h @@ -5,130 +5,16 @@ #import #import #import "FIATransactionCache.h" +#import "FLTPaymentQueueHandlerProtocol.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTTransactionCacheProtocol.h" @class SKPaymentTransaction; NS_ASSUME_NONNULL_BEGIN -typedef void (^TransactionsUpdated)(NSArray *transactions); -typedef void (^TransactionsRemoved)(NSArray *transactions); -typedef void (^RestoreTransactionFailed)(NSError *error); -typedef void (^RestoreCompletedTransactionsFinished)(void); -typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product); -typedef void (^UpdatedDownloads)(NSArray *downloads); - -@interface FIAPaymentQueueHandler : NSObject - -@property(NS_NONATOMIC_IOSONLY, weak, nullable) id delegate API_AVAILABLE( - ios(13.0), macos(10.15), watchos(6.2)); -@property(nonatomic, readonly, nullable) - SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2)); - -/// Creates a new FIAPaymentQueueHandler initialized with an empty -/// FIATransactionCache. -/// -/// @param queue The SKPaymentQueue instance connected to the App Store and -/// responsible for processing transactions. -/// @param transactionsUpdated Callback method that is called each time the App -/// Store indicates transactions are updated. -/// @param transactionsRemoved Callback method that is called each time the App -/// Store indicates transactions are removed. -/// @param restoreTransactionFailed Callback method that is called each time -/// the App Store indicates transactions failed -/// to restore. -/// @param restoreCompletedTransactionsFinished Callback method that is called -/// each time the App Store -/// indicates restoring of -/// transactions has finished. -/// @param shouldAddStorePayment Callback method that is called each time an -/// in-app purchase has been initiated from the -/// App Store. -/// @param updatedDownloads Callback method that is called each time the App -/// Store indicates downloads are updated. -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue - transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated - transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved - restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed - restoreCompletedTransactionsFinished: - (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished - shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment - updatedDownloads:(nullable UpdatedDownloads)updatedDownloads - DEPRECATED_MSG_ATTRIBUTE( - "Use the " - "'initWithQueue:transactionsUpdated:transactionsRemoved:restoreTransactionsFinished:" - "shouldAddStorePayment:updatedDownloads:transactionCache:' message instead."); - -/// Creates a new FIAPaymentQueueHandler. -/// -/// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" -/// callbacks are only called while actively observing transactions. To start -/// observing transactions send the "startObservingPaymentQueue" message. -/// Sending the "stopObservingPaymentQueue" message will stop actively -/// observing transactions. When transactions are not observed they are cached -/// to the "transactionCache" and will be delivered via the -/// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" -/// callbacks as soon as the "startObservingPaymentQueue" message arrives. -/// -/// Note: cached transactions that are not processed when the application is -/// killed will be delivered again by the App Store as soon as the application -/// starts again. -/// -/// @param queue The SKPaymentQueue instance connected to the App Store and -/// responsible for processing transactions. -/// @param transactionsUpdated Callback method that is called each time the App -/// Store indicates transactions are updated. -/// @param transactionsRemoved Callback method that is called each time the App -/// Store indicates transactions are removed. -/// @param restoreTransactionFailed Callback method that is called each time -/// the App Store indicates transactions failed -/// to restore. -/// @param restoreCompletedTransactionsFinished Callback method that is called -/// each time the App Store -/// indicates restoring of -/// transactions has finished. -/// @param shouldAddStorePayment Callback method that is called each time an -/// in-app purchase has been initiated from the -/// App Store. -/// @param updatedDownloads Callback method that is called each time the App -/// Store indicates downloads are updated. -/// @param transactionCache An empty [FIATransactionCache] instance that is -/// responsible for keeping track of transactions that -/// arrive when not actively observing transactions. -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue - transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated - transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved - restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed - restoreCompletedTransactionsFinished: - (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished - shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment - updatedDownloads:(nullable UpdatedDownloads)updatedDownloads - transactionCache:(nonnull FIATransactionCache *)transactionCache; -// Can throw exceptions if the transaction type is purchasing, should always used in a @try block. -- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; -- (void)restoreTransactions:(nullable NSString *)applicationName; -- (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos); -- (NSArray *)getUnfinishedTransactions; - -// This method needs to be called before any other methods. -- (void)startObservingPaymentQueue; -// Call this method when the Flutter app is no longer listening -- (void)stopObservingPaymentQueue; - -// Appends a payment to the SKPaymentQueue. -// -// @param payment Payment object to be added to the payment queue. -// @return whether "addPayment" was successful. -- (BOOL)addPayment:(SKPayment *)payment; - -// Displays the price consent sheet. -// -// The price consent sheet is only displayed when the following -// is true: -// - You have increased the price of the subscription in App Store Connect. -// - The subscriber has not yet responded to a price consent query. -// Otherwise the method has no effect. -- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos); - +@interface FIAPaymentQueueHandler + : NSObject @end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m index 60056575089..e221ce0695c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m @@ -10,34 +10,34 @@ @interface FIAPaymentQueueHandler () /// The SKPaymentQueue instance connected to the App Store and responsible for processing /// transactions. -@property(strong, nonatomic) SKPaymentQueue *queue; +@property(nonatomic, strong) SKPaymentQueue *queue; /// Callback method that is called each time the App Store indicates transactions are updated. -@property(nullable, copy, nonatomic) TransactionsUpdated transactionsUpdated; +@property(nonatomic, nullable, copy) TransactionsUpdated transactionsUpdated; /// Callback method that is called each time the App Store indicates transactions are removed. -@property(nullable, copy, nonatomic) TransactionsRemoved transactionsRemoved; +@property(nonatomic, nullable, copy) TransactionsRemoved transactionsRemoved; /// Callback method that is called each time the App Store indicates transactions failed to restore. -@property(nullable, copy, nonatomic) RestoreTransactionFailed restoreTransactionFailed; +@property(nonatomic, nullable, copy) RestoreTransactionFailed restoreTransactionFailed; /// Callback method that is called each time the App Store indicates restoring of transactions has /// finished. -@property(nullable, copy, nonatomic) +@property(nonatomic, nullable, copy) RestoreCompletedTransactionsFinished paymentQueueRestoreCompletedTransactionsFinished; /// Callback method that is called each time an in-app purchase has been initiated from the App /// Store. -@property(nullable, copy, nonatomic) ShouldAddStorePayment shouldAddStorePayment; +@property(nonatomic, nullable, copy) ShouldAddStorePayment shouldAddStorePayment; /// Callback method that is called each time the App Store indicates downloads are updated. -@property(nullable, copy, nonatomic) UpdatedDownloads updatedDownloads; +@property(nonatomic, nullable, copy) UpdatedDownloads updatedDownloads; /// The transaction cache responsible for caching transactions. /// /// Keeps track of transactions that arrive when the Flutter client is not /// actively observing for transactions. -@property(strong, nonatomic, nonnull) FIATransactionCache *transactionCache; +@property(nonatomic, strong, nonnull) FIATransactionCache *transactionCache; /// Indicates if the Flutter client is observing transactions. /// @@ -51,7 +51,9 @@ @interface FIAPaymentQueueHandler () @implementation FIAPaymentQueueHandler -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue +@synthesize delegate; + +- (instancetype)initWithQueue:(nonnull id)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed @@ -66,10 +68,10 @@ - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue restoreCompletedTransactionsFinished:restoreCompletedTransactionsFinished shouldAddStorePayment:shouldAddStorePayment updatedDownloads:updatedDownloads - transactionCache:[[FIATransactionCache alloc] init]]; + transactionCache:[[DefaultTransactionCache alloc] init]]; } -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue +- (instancetype)initWithQueue:(nonnull id)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed @@ -77,7 +79,7 @@ - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment updatedDownloads:(nullable UpdatedDownloads)updatedDownloads - transactionCache:(nonnull FIATransactionCache *)transactionCache { + transactionCache:(nonnull id)transactionCache { self = [super init]; if (self) { _queue = queue; @@ -170,7 +172,7 @@ - (void)presentCodeRedemptionSheet { #endif #if TARGET_OS_IOS -- (void)showPriceConsentIfNeeded { +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4)) { [self.queue showPriceConsentIfNeeded]; } #endif @@ -233,7 +235,7 @@ - (BOOL)paymentQueue:(SKPaymentQueue *)queue return self.queue.transactions; } -- (SKStorefront *)storefront { +- (SKStorefront *)storefront API_AVAILABLE(ios(13.0)) { return self.queue.storefront; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h deleted file mode 100644 index ca090b6b992..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h +++ /dev/null @@ -1,32 +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 "FIAPRequestHandler.h" -#import "InAppPurchasePlugin.h" - -@interface InAppPurchasePlugin () - -// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after -// the request is finished. -@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; - -// Callback channel to dart used for when a function from the transaction observer is triggered. -@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; - -// Callback channel to dart used for when a function from the transaction observer is triggered. -@property(copy, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); - -// Convenience initializer with dependancy injection -- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager - handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory; - -// Transaction observer methods -- (void)handleTransactionsUpdated:(NSArray *)transactions; -- (void)handleTransactionsRemoved:(NSArray *)transactions; -- (void)handleTransactionRestoreFailed:(NSError *)error; -- (void)restoreCompletedTransactionsFinished; -- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; - -@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h deleted file mode 100644 index a2b9cb0eb93..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h +++ /dev/null @@ -1,23 +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. - -#if TARGET_OS_OSX -#import -#else -#import -#endif -#import "messages.g.h" - -@class FIAPaymentQueueHandler; -@class FIAPReceiptManager; - -@interface InAppPurchasePlugin : NSObject - -@property(strong, nonatomic) FIAPaymentQueueHandler *paymentQueueHandler; - -- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager - NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m deleted file mode 100644 index 2e2abaaed35..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ /dev/null @@ -1,404 +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 "InAppPurchasePlugin.h" -#import -#import "FIAObjectTranslator.h" -#import "FIAPPaymentQueueDelegate.h" -#import "FIAPReceiptManager.h" -#import "FIAPRequestHandler.h" -#import "FIAPaymentQueueHandler.h" -#import "InAppPurchasePlugin+TestOnly.h" - -@interface InAppPurchasePlugin () - -// After querying the product, the available products will be saved in the map to be used -// for purchase. -@property(strong, nonatomic, readonly) NSMutableDictionary *productsCache; - -// Callback channel to dart used for when a function from the payment queue delegate is triggered. -@property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel; -@property(strong, nonatomic, readonly) NSObject *registrar; - -@property(strong, nonatomic, readonly) FIAPReceiptManager *receiptManager; -@property(strong, nonatomic, readonly) - FIAPPaymentQueueDelegate *paymentQueueDelegate API_AVAILABLE(ios(13)) - API_UNAVAILABLE(tvos, macos, watchos); - -@end - -@implementation InAppPurchasePlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase" - binaryMessenger:[registrar messenger]]; - - InAppPurchasePlugin *instance = [[InAppPurchasePlugin alloc] initWithRegistrar:registrar]; - [registrar addMethodCallDelegate:instance channel:channel]; - [registrar addApplicationDelegate:instance]; - SetUpInAppPurchaseAPI(registrar.messenger, instance); -} - -- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager { - self = [super init]; - _receiptManager = receiptManager; - _requestHandlers = [NSMutableSet new]; - _productsCache = [NSMutableDictionary new]; - _handlerFactory = ^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; - }; - return self; -} - -- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager - handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory { - self = [self initWithReceiptManager:receiptManager]; - _handlerFactory = [handlerFactory copy]; - return self; -} - -- (instancetype)initWithRegistrar:(NSObject *)registrar { - self = [self initWithReceiptManager:[FIAPReceiptManager new]]; - _registrar = registrar; - - __weak typeof(self) weakSelf = self; - _paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueue defaultQueue] - transactionsUpdated:^(NSArray *_Nonnull transactions) { - [weakSelf handleTransactionsUpdated:transactions]; - } - transactionRemoved:^(NSArray *_Nonnull transactions) { - [weakSelf handleTransactionsRemoved:transactions]; - } - restoreTransactionFailed:^(NSError *_Nonnull error) { - [weakSelf handleTransactionRestoreFailed:error]; - } - restoreCompletedTransactionsFinished:^{ - [weakSelf restoreCompletedTransactionsFinished]; - } - shouldAddStorePayment:^BOOL(SKPayment *payment, SKProduct *product) { - return [weakSelf shouldAddStorePayment:payment product:product]; - } - updatedDownloads:^void(NSArray *_Nonnull downloads) { - [weakSelf updatedDownloads:downloads]; - } - transactionCache:[[FIATransactionCache alloc] init]]; - - _transactionObserverCallbackChannel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase" - binaryMessenger:[registrar messenger]]; - return self; -} - -- (nullable NSNumber *)canMakePaymentsWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return @([SKPaymentQueue canMakePayments]); -} - -- (nullable NSArray *)transactionsWithError: - (FlutterError *_Nullable *_Nonnull)error { - NSArray *transactions = - [self.paymentQueueHandler getUnfinishedTransactions]; - NSMutableArray *transactionMaps = [[NSMutableArray alloc] init]; - for (SKPaymentTransaction *transaction in transactions) { - [transactionMaps addObject:[FIAObjectTranslator convertTransactionToPigeon:transaction]]; - } - return transactionMaps; -} - -- (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable *_Nonnull)error - API_AVAILABLE(ios(13.0), macos(10.15)) { - SKStorefront *storefront = self.paymentQueueHandler.storefront; - if (!storefront) { - return nil; - } - return [FIAObjectTranslator convertStorefrontToPigeon:storefront]; -} - -- (void)startProductRequestProductIdentifiers:(NSArray *)productIdentifiers - completion:(void (^)(SKProductsResponseMessage *_Nullable, - FlutterError *_Nullable))completion { - SKProductsRequest *request = - [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = self.handlerFactory(request); - [self.requestHandlers addObject:handler]; - __weak typeof(self) weakSelf = self; - - [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, - NSError *_Nullable startProductRequestError) { - FlutterError *error = nil; - if (startProductRequestError != nil) { - error = [FlutterError errorWithCode:@"storekit_getproductrequest_platform_error" - message:startProductRequestError.localizedDescription - details:startProductRequestError.description]; - completion(nil, error); - return; - } - if (!response) { - error = [FlutterError errorWithCode:@"storekit_platform_no_response" - message:@"Failed to get SKProductResponse in startRequest " - @"call. Error occured on iOS platform" - details:productIdentifiers]; - completion(nil, error); - return; - } - for (SKProduct *product in response.products) { - [self.productsCache setObject:product forKey:product.productIdentifier]; - } - - completion([FIAObjectTranslator convertProductsResponseToPigeon:response], error); - [weakSelf.requestHandlers removeObject:handler]; - }]; -} - -- (void)addPaymentPaymentMap:(nonnull NSDictionary *)paymentMap - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - NSString *productID = [paymentMap objectForKey:@"productIdentifier"]; - // When a product is already fetched, we create a payment object with - // the product to process the payment. - SKProduct *product = [self getProduct:productID]; - if (!product) { - *error = [FlutterError - errorWithCode:@"storekit_invalid_payment_object" - message: - @"You have requested a payment for an invalid product. Either the " - @"`productIdentifier` of the payment is not valid or the product has not been " - @"fetched before adding the payment to the payment queue." - details:paymentMap]; - return; - } - - SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; - payment.applicationUsername = [paymentMap objectForKey:@"applicationUsername"]; - NSNumber *quantity = [paymentMap objectForKey:@"quantity"]; - payment.quantity = (quantity != nil) ? quantity.integerValue : 1; - NSNumber *simulatesAskToBuyInSandbox = [paymentMap objectForKey:@"simulatesAskToBuyInSandbox"]; - payment.simulatesAskToBuyInSandbox = (id)simulatesAskToBuyInSandbox == (id)[NSNull null] - ? NO - : [simulatesAskToBuyInSandbox boolValue]; - - if (@available(iOS 12.2, *)) { - NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap - forKey:@"paymentDiscount"]; - NSString *errorMsg = nil; - SKPaymentDiscount *paymentDiscount = - [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&errorMsg]; - - if (errorMsg) { - *error = [FlutterError - errorWithCode:@"storekit_invalid_payment_discount_object" - message:[NSString stringWithFormat:@"You have requested a payment and specified a " - @"payment discount with invalid properties. %@", - errorMsg] - details:paymentMap]; - return; - } - - payment.paymentDiscount = paymentDiscount; - } - if (![self.paymentQueueHandler addPayment:payment]) { - *error = [FlutterError - errorWithCode:@"storekit_duplicate_product_object" - message:@"There is a pending transaction for the same product identifier. Please " - @"either wait for it to be finished or finish it manually using " - @"`completePurchase` to avoid edge cases." - - details:paymentMap]; - return; - } -} - -- (void)finishTransactionFinishMap:(nonnull NSDictionary *)finishMap - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - NSString *transactionIdentifier = [finishMap objectForKey:@"transactionIdentifier"]; - NSString *productIdentifier = [finishMap objectForKey:@"productIdentifier"]; - - NSArray *pendingTransactions = - [self.paymentQueueHandler getUnfinishedTransactions]; - - for (SKPaymentTransaction *transaction in pendingTransactions) { - // If the user cancels the purchase dialog we won't have a transactionIdentifier. - // So if it is null AND a transaction in the pendingTransactions list has - // also a null transactionIdentifier we check for equal product identifiers. - if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier] || - ([transactionIdentifier isEqual:[NSNull null]] && - transaction.transactionIdentifier == nil && - [transaction.payment.productIdentifier isEqualToString:productIdentifier])) { - @try { - [self.paymentQueueHandler finishTransaction:transaction]; - } @catch (NSException *e) { - *error = [FlutterError errorWithCode:@"storekit_finish_transaction_exception" - message:e.name - details:e.description]; - return; - } - } - } -} - -- (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName - error:(FlutterError *_Nullable __autoreleasing *_Nonnull) - error { - [self.paymentQueueHandler restoreTransactions:applicationUserName]; -} - -- (void)presentCodeRedemptionSheetWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { -#if TARGET_OS_IOS - [self.paymentQueueHandler presentCodeRedemptionSheet]; -#endif -} - -- (nullable NSString *)retrieveReceiptDataWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - FlutterError *flutterError; - NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&flutterError]; - if (flutterError) { - *error = flutterError; - return nil; - } - return receiptData; -} - -- (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperties - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - SKReceiptRefreshRequest *request; - if (receiptProperties) { - // if recieptProperties is not null, this call is for testing. - NSMutableDictionary *properties = [NSMutableDictionary new]; - properties[SKReceiptPropertyIsExpired] = receiptProperties[@"isExpired"]; - properties[SKReceiptPropertyIsRevoked] = receiptProperties[@"isRevoked"]; - properties[SKReceiptPropertyIsVolumePurchase] = receiptProperties[@"isVolumePurchase"]; - request = [self getRefreshReceiptRequest:properties]; - } else { - request = [self getRefreshReceiptRequest:nil]; - } - - FIAPRequestHandler *handler = self.handlerFactory(request); - [self.requestHandlers addObject:handler]; - __weak typeof(self) weakSelf = self; - [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, - NSError *_Nullable error) { - FlutterError *requestError; - if (error) { - requestError = [FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" - message:error.localizedDescription - details:error.description]; - completion(requestError); - return; - } - completion(nil); - [weakSelf.requestHandlers removeObject:handler]; - }]; -} - -- (void)startObservingPaymentQueueWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - [_paymentQueueHandler startObservingPaymentQueue]; -} - -- (void)stopObservingPaymentQueueWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - [_paymentQueueHandler stopObservingPaymentQueue]; -} - -- (void)registerPaymentQueueDelegateWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { -#if TARGET_OS_IOS - if (@available(iOS 13.0, *)) { - _paymentQueueDelegateCallbackChannel = [FlutterMethodChannel - methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate" - binaryMessenger:[_registrar messenger]]; - - _paymentQueueDelegate = [[FIAPPaymentQueueDelegate alloc] - initWithMethodChannel:_paymentQueueDelegateCallbackChannel]; - _paymentQueueHandler.delegate = _paymentQueueDelegate; - } -#endif -} - -- (void)removePaymentQueueDelegateWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - if (@available(iOS 13.0, *)) { - _paymentQueueHandler.delegate = nil; - } - _paymentQueueDelegate = nil; - _paymentQueueDelegateCallbackChannel = nil; -} - -- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { -#if TARGET_OS_IOS - if (@available(iOS 13.4, *)) { - [_paymentQueueHandler showPriceConsentIfNeeded]; - } -#endif -} - -- (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { - id value = dictionary[key]; - return [value isKindOfClass:[NSNull class]] ? nil : value; -} - -#pragma mark - transaction observer: - -- (void)handleTransactionsUpdated:(NSArray *)transactions { - NSMutableArray *maps = [NSMutableArray new]; - for (SKPaymentTransaction *transaction in transactions) { - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]]; - } - [self.transactionObserverCallbackChannel invokeMethod:@"updatedTransactions" arguments:maps]; -} - -- (void)handleTransactionsRemoved:(NSArray *)transactions { - NSMutableArray *maps = [NSMutableArray new]; - for (SKPaymentTransaction *transaction in transactions) { - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]]; - } - [self.transactionObserverCallbackChannel invokeMethod:@"removedTransactions" arguments:maps]; -} - -- (void)handleTransactionRestoreFailed:(NSError *)error { - [self.transactionObserverCallbackChannel - invokeMethod:@"restoreCompletedTransactionsFailed" - arguments:[FIAObjectTranslator getMapFromNSError:error]]; -} - -- (void)restoreCompletedTransactionsFinished { - [self.transactionObserverCallbackChannel - invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" - arguments:nil]; -} - -- (void)updatedDownloads:(NSArray *)downloads { - NSLog(@"Received an updatedDownloads callback, but downloads are not supported."); -} - -- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product { - // We always return NO here. And we send the message to dart to process the payment; and we will - // have a interception method that deciding if the payment should be processed (implemented by the - // programmer). - [self.productsCache setObject:product forKey:product.productIdentifier]; - [self.transactionObserverCallbackChannel - invokeMethod:@"shouldAddStorePayment" - arguments:@{ - @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], - @"product" : [FIAObjectTranslator getMapFromSKProduct:product] - }]; - return NO; -} - -#pragma mark - dependency injection (for unit testing) - -- (SKProductsRequest *)getProductRequestWithIdentifiers:(NSSet *)identifiers { - return [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers]; -} - -- (SKProduct *)getProduct:(NSString *)productID { - return [self.productsCache objectForKey:productID]; -} - -- (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { - return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; -} -@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift new file mode 100644 index 00000000000..4ebd8828184 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -0,0 +1,435 @@ +// 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 Foundation +import StoreKit + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#endif + +public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { + private let receiptManager: FIAPReceiptManager + private var productsCache: NSMutableDictionary = [:] + private var paymentQueueDelegateCallbackChannel: FlutterMethodChannel? + // note - the type should be FIAPPaymentQueueDelegate, but this is only available >= iOS 13, + // FIAPPaymentQueueDelegate only gets set/used in registerPaymentQueueDelegateWithError or removePaymentQueueDelegateWithError, which both are ios13+ only + private var paymentQueueDelegate: Any? + // Swift sets do not accept protocols, only concrete implementations + // TODO(louisehsu): Change it back to a set when removing obj-c dependancies from this file via type erasure + private var requestHandlers = NSHashTable() + private var handlerFactory: ((SKRequest) -> FLTRequestHandlerProtocol) + // TODO(louisehsu): Once tests are migrated to swift, we can use @testable import, and make theses vars private again and remove all instances of @objc + @objc + public var registrar: FlutterPluginRegistrar? + // This property is optional, as it requires self to exist to be initialized. + @objc + public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? + // This property is optional, as it needs to be set during plugin registration, and can't be directly initialized. + @objc + public var transactionObserverCallbackChannel: FLTMethodChannelProtocol? + + public static func register(with registrar: FlutterPluginRegistrar) { + #if os(iOS) + let messenger = registrar.messenger() + #endif + #if os(macOS) + let messenger = registrar.messenger + #endif + let channel = FlutterMethodChannel( + name: "plugins.flutter.io/in_app_purchase", + binaryMessenger: messenger) + let instance = InAppPurchasePlugin(registrar: registrar) + registrar.addMethodCallDelegate(instance, channel: channel) + registrar.addApplicationDelegate(instance) + SetUpInAppPurchaseAPI(messenger, instance) + } + + @objc + // This init is used for tests + public init( + receiptManager: FIAPReceiptManager, + handlerFactory: @escaping (SKRequest) -> FLTRequestHandlerProtocol = { + DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: $0)) + } + ) { + self.receiptManager = receiptManager + self.handlerFactory = handlerFactory + super.init() + } + + // This init gets called during plugin registration + public convenience init(registrar: FlutterPluginRegistrar) { + self.init(receiptManager: FIAPReceiptManager()) + self.registrar = registrar + + self.paymentQueueHandler = FIAPaymentQueueHandler( + queue: DefaultPaymentQueue(queue: SKPaymentQueue.default()), + transactionsUpdated: { [weak self] transactions in + self?.handleTransactionsUpdated(transactions) + }, + transactionRemoved: { [weak self] transactions in + self?.handleTransactionsRemoved(transactions) + }, + restoreTransactionFailed: { [weak self] error in + self?.handleTransactionRestoreFailed(error as NSError) + }, + restoreCompletedTransactionsFinished: { [weak self] in + self?.restoreCompletedTransactionsFinished() + }, + shouldAddStorePayment: { [weak self] (payment, product) -> Bool in + return self?.shouldAddStorePayment(payment: payment, product: product) ?? false + }, + updatedDownloads: { [weak self] _ in + self?.updatedDownloads() + }, + transactionCache: DefaultTransactionCache(cache: FIATransactionCache())) + #if os(iOS) + let messenger = registrar.messenger() + #endif + #if os(macOS) + let messenger = registrar.messenger + #endif + transactionObserverCallbackChannel = DefaultMethodChannel( + channel: FlutterMethodChannel( + name: "plugins.flutter.io/in_app_purchase", + binaryMessenger: messenger) + ) + } + + // MARK: - Pigeon Functions + + public func canMakePaymentsWithError(_ error: AutoreleasingUnsafeMutablePointer) + -> NSNumber? + { + return SKPaymentQueue.canMakePayments() as NSNumber + } + + public func transactionsWithError(_ error: AutoreleasingUnsafeMutablePointer) + -> [SKPaymentTransactionMessage]? + { + return getPaymentQueueHandler() + .getUnfinishedTransactions() + .compactMap { + FIAObjectTranslator.convertTransaction(toPigeon: $0) + } + } + + public func storefrontWithError(_ error: AutoreleasingUnsafeMutablePointer) + -> SKStorefrontMessage? + { + if #available(iOS 13.0, *), let storefront = getPaymentQueueHandler().storefront { + return FIAObjectTranslator.convertStorefront(toPigeon: storefront) + } + return nil + } + + public func startProductRequestProductIdentifiers( + _ productIdentifiers: [String], + completion: @escaping (SKProductsResponseMessage?, FlutterError?) -> Void + ) { + let request = getProductRequest(withIdentifiers: Set(productIdentifiers)) + let handler = handlerFactory(request) + requestHandlers.add(handler) + + handler.startProductRequest { [weak self] response, startProductRequestError in + guard let self = self else { return } + if let startProductRequestError = startProductRequestError { + let error = FlutterError( + code: "storekit_getproductrequest_platform_error", + message: startProductRequestError.localizedDescription, + details: startProductRequestError.localizedDescription) + completion(nil, error) + return + } + + guard let response = response else { + let error = FlutterError( + code: "storekit_platform_no_response", + message: + "Failed to get SKProductResponse in startRequest call. Error occurred on iOS platform", + details: productIdentifiers) + completion(nil, error) + return + } + + for product in response.products { + self.productsCache[product.productIdentifier] = product + } + + if #available(iOS 12.2, *) { + if let responseMessage = FIAObjectTranslator.convertProductsResponse(toPigeon: response) { + completion(responseMessage, nil) + } + } + self.requestHandlers.remove(handler) + } + } + + public func addPaymentPaymentMap( + _ paymentMap: [String: Any], error: AutoreleasingUnsafeMutablePointer + ) { + guard let productID = paymentMap["productIdentifier"] as? String else { + error.pointee = FlutterError( + code: "storekit_missing_product_identifier", + message: "The `productIdentifier` is missing from the payment map.", + details: paymentMap) + return + } + + guard let product = self.getProduct(productID: productID) else { + error.pointee = FlutterError( + code: "storekit_invalid_payment_object", + message: + "You have requested a payment for an invalid product. Either the `productIdentifier` of the payment is not valid or the product has not been fetched before adding the payment to the payment queue.", + details: paymentMap) + return + } + + let payment = SKMutablePayment(product: product) + payment.applicationUsername = paymentMap["applicationUsername"] as? String + payment.quantity = paymentMap["quantity"] as? Int ?? 1 + payment.simulatesAskToBuyInSandbox = paymentMap["simulatesAskToBuyInSandbox"] as? Bool ?? false + + if #available(iOS 12.2, *) { + if let paymentDiscountMap = paymentMap["paymentDiscount"] as? [String: Any], + !paymentDiscountMap.isEmpty + { + var invalidError: NSString? + if let paymentDiscount = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: paymentDiscountMap, withError: &invalidError) + { + payment.paymentDiscount = paymentDiscount + } else if let invalidError = invalidError { + error.pointee = FlutterError( + code: "storekit_invalid_payment_discount_object", + message: + "You have requested a payment and specified a payment discount with invalid properties. \(invalidError)", + details: paymentMap) + return + } + } + } + + if !getPaymentQueueHandler().add(payment) { + error.pointee = FlutterError( + code: "storekit_duplicate_product_object", + message: + "There is a pending transaction for the same product identifier. Please either wait for it to be finished or finish it manually using `completePurchase` to avoid edge cases.", + details: paymentMap) + } + } + + // TODO(louisehsu): Once tests and pigeon are migrated to Swift, ensure the param type is [String:String] instead of [String:String?] + public func finishTransactionFinishMap( + _ finishMap: [String: Any], error: AutoreleasingUnsafeMutablePointer + ) { + + // TODO(louisehsu): This is a workaround for objc pigeon's NSNull support. Once we move to swift pigeon, this can be removed. + let castedFinishMap: [String: String] = finishMap.compactMapValues { value in + if let _ = value as? NSNull { + return nil + } else if let stringValue = value as? String { + return stringValue + } + fatalError("This dict should only contain either NSNull or String") + } + let productIdentifier = castedFinishMap["productIdentifier"] + let transactionIdentifier = castedFinishMap["transactionIdentifier"] + let pendingTransactions = getPaymentQueueHandler().getUnfinishedTransactions() + + for transaction in pendingTransactions { + // If the user cancels the purchase dialog we won't have a transactionIdentifier. + // So if it is null AND a transaction in the pendingTransactions list has + // also a null transactionIdentifier we check for equal product identifiers. + if transaction.transactionIdentifier == transactionIdentifier + || (transactionIdentifier == nil + && transaction.transactionIdentifier == nil + && transaction.payment.productIdentifier == productIdentifier) + { + getPaymentQueueHandler().finish(transaction) + } + } + } + + public func restoreTransactionsApplicationUserName( + _ applicationUserName: String?, error: AutoreleasingUnsafeMutablePointer + ) { + getPaymentQueueHandler().restoreTransactions(applicationUserName) + } + + public func presentCodeRedemptionSheetWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) { + #if os(iOS) + getPaymentQueueHandler().presentCodeRedemptionSheet() + #endif + } + + public func retrieveReceiptDataWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) -> String? { + var flutterError: FlutterError? = nil + if let receiptData = receiptManager.retrieveReceiptWithError(&flutterError) { + return receiptData + } else { + error.pointee = flutterError + return nil + } + } + + public func refreshReceiptReceiptProperties( + _ receiptProperties: [String: Any]?, completion: @escaping (FlutterError?) -> Void + ) { + let properties = receiptProperties?.compactMapValues { $0 } ?? [:] + let request = getRefreshReceiptRequest(properties: properties.isEmpty ? nil : properties) + let handler = handlerFactory(request) + requestHandlers.add(handler) + handler.startProductRequest { [weak self] response, error in + if let error = error { + let requestError = FlutterError( + code: "storekit_refreshreceiptrequest_platform_error", + message: error.localizedDescription, + details: error.localizedDescription) + completion(requestError) + return + } + completion(nil) + self?.requestHandlers.remove(handler) + } + } + + public func startObservingPaymentQueueWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) { + getPaymentQueueHandler().startObservingPaymentQueue() + } + + public func stopObservingPaymentQueueWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) { + getPaymentQueueHandler().stopObservingPaymentQueue() + } + + public func registerPaymentQueueDelegateWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) { + #if os(iOS) + if #available(iOS 13.0, *) { + guard let messenger = registrar?.messenger() else { + fatalError("registrar.messenger can not be nil.") + } + paymentQueueDelegateCallbackChannel = FlutterMethodChannel( + name: "plugins.flutter.io/in_app_purchase_payment_queue_delegate", + binaryMessenger: messenger) + + guard let unwrappedChannel = paymentQueueDelegateCallbackChannel else { + fatalError("paymentQueueDelegateCallbackChannel can not be nil.") + } + paymentQueueDelegate = FIAPPaymentQueueDelegate( + methodChannel: DefaultMethodChannel(channel: unwrappedChannel)) + + getPaymentQueueHandler().delegate = paymentQueueDelegate as? SKPaymentQueueDelegate + } + #endif + } + + public func removePaymentQueueDelegateWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) { + #if os(iOS) + if #available(iOS 13.0, *) { + paymentQueueDelegateCallbackChannel = nil + getPaymentQueueHandler().delegate = nil + paymentQueueDelegate = nil + } + #endif + } + + public func showPriceConsentIfNeededWithError( + _ error: AutoreleasingUnsafeMutablePointer + ) { + #if os(iOS) + if #available(iOS 13.4, *) { + getPaymentQueueHandler().showPriceConsentIfNeeded() + } + #endif + } + + @objc + public func handleTransactionsUpdated(_ transactions: [SKPaymentTransaction]) { + let translatedTransactions = transactions.map { + FIAObjectTranslator.getMapFrom($0) + } + transactionObserverCallbackChannel?.invokeMethod( + "updatedTransactions", arguments: translatedTransactions) + } + + @objc + public func handleTransactionsRemoved(_ transactions: [SKPaymentTransaction]) { + let translatedTransactions = transactions.map { + FIAObjectTranslator.getMapFrom($0) + } + transactionObserverCallbackChannel?.invokeMethod( + "removedTransactions", arguments: translatedTransactions) + } + + @objc + public func handleTransactionRestoreFailed(_ error: NSError) { + transactionObserverCallbackChannel?.invokeMethod( + "restoreCompletedTransactionsFailed", arguments: FIAObjectTranslator.getMapFrom(error)) + } + + @objc + public func restoreCompletedTransactionsFinished() { + transactionObserverCallbackChannel?.invokeMethod( + "paymentQueueRestoreCompletedTransactionsFinished", arguments: nil) + } + + @objc + public func shouldAddStorePayment(payment: SKPayment, product: SKProduct) -> Bool { + productsCache[product.productIdentifier] = product + transactionObserverCallbackChannel?.invokeMethod( + "shouldAddStorePayment", + arguments: [ + "payment": FIAObjectTranslator.getMapFrom(payment), + "product": FIAObjectTranslator.getMapFrom(product), + ]) + return false + } + + public func updatedDownloads() { + NSLog("Received an updatedDownloads callback, but downloads are not supported.") + } + + // MARK: - Methods exposed for testing + func getProduct(productID: String) -> SKProduct? { + return self.productsCache[productID] as? SKProduct + } + + func getProductRequest(withIdentifiers productIdentifiers: Set) -> SKProductsRequest { + return SKProductsRequest(productIdentifiers: productIdentifiers) + } + + func getRefreshReceiptRequest(properties: [String: Any]?) -> SKReceiptRefreshRequest { + return SKReceiptRefreshRequest(receiptProperties: properties) + } + + // MARK: - Private convenience methods + private func getNonNullValue(from dictionary: [String: Any], forKey key: String) -> Any? { + let value = dictionary[key] + return value is NSNull ? nil : value + } + + private func getPaymentQueueHandler() -> FLTPaymentQueueHandlerProtocol { + guard let paymentQueueHandler = self.paymentQueueHandler else { + fatalError( + "paymentQueueHandler can't be nil. Please ensure you're using init(registrar: FlutterPluginRegistrar)" + ) + } + return paymentQueueHandler + } +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h new file mode 100644 index 00000000000..12856435208 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h @@ -0,0 +1,33 @@ +// 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. + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN +/// A protocol that wraps FlutterMethodChannel. +@protocol FLTMethodChannelProtocol + +/// Invokes the specified Flutter method with the specified arguments, expecting +/// an asynchronous result. +- (void)invokeMethod:(NSString *)method arguments:(id _Nullable)arguments; + +/// Invokes the specified Flutter method with the specified arguments and specified callback +- (void)invokeMethod:(NSString *)method + arguments:(id _Nullable)arguments + result:(FlutterResult _Nullable)callback; + +@end + +/// The default method channel that wraps FlutterMethodChannel +@interface DefaultMethodChannel : NSObject + +/// Initialize this wrapper with a FlutterMethodChannel +- (instancetype)initWithChannel:(FlutterMethodChannel *)channel; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m new file mode 100644 index 00000000000..17e0e0803fc --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTMethodChannelProtocol.h" + +@interface DefaultMethodChannel () +/// The wrapped FlutterMethodChannel +@property(nonatomic, strong) FlutterMethodChannel *channel; +@end + +@implementation DefaultMethodChannel + +- (instancetype)initWithChannel:(nonnull FlutterMethodChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + } + return self; +} + +- (void)invokeMethod:(nonnull NSString *)method arguments:(id _Nullable)arguments { + [self.channel invokeMethod:method arguments:arguments]; +} + +- (void)invokeMethod:(nonnull NSString *)method + arguments:(id _Nullable)arguments + result:(FlutterResult _Nullable)callback { + [self.channel invokeMethod:method arguments:arguments result:callback]; +} + +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h new file mode 100644 index 00000000000..f11b1a09ce4 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h @@ -0,0 +1,107 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import "FIATransactionCache.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTTransactionCacheProtocol.h" + +NS_ASSUME_NONNULL_BEGIN +typedef void (^TransactionsUpdated)(NSArray *transactions); +typedef void (^TransactionsRemoved)(NSArray *transactions); +typedef void (^RestoreTransactionFailed)(NSError *error); +typedef void (^RestoreCompletedTransactionsFinished)(void); +typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product); +typedef void (^UpdatedDownloads)(NSArray *downloads); + +/// A protocol that conforms to SKPaymentTransactionObserver and handles SKPaymentQueue methods +@protocol FLTPaymentQueueHandlerProtocol +/// An object that provides information needed to complete transactions. +@property(nonatomic, weak, nullable) id delegate API_AVAILABLE( + ios(13.0), macos(10.15), watchos(6.2)); +/// An object containing the location and unique identifier of an Apple App Store storefront. +@property(nonatomic, readonly, nullable) + SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2)); + +/// Creates a new FIAPaymentQueueHandler. +/// +/// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" +/// callbacks are only called while actively observing transactions. To start +/// observing transactions send the "startObservingPaymentQueue" message. +/// Sending the "stopObservingPaymentQueue" message will stop actively +/// observing transactions. When transactions are not observed they are cached +/// to the "transactionCache" and will be delivered via the +/// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" +/// callbacks as soon as the "startObservingPaymentQueue" message arrives. +/// +/// Note: cached transactions that are not processed when the application is +/// killed will be delivered again by the App Store as soon as the application +/// starts again. +/// +/// @param queue The SKPaymentQueue instance connected to the App Store and +/// responsible for processing transactions. +/// @param transactionsUpdated Callback method that is called each time the App +/// Store indicates transactions are updated. +/// @param transactionsRemoved Callback method that is called each time the App +/// Store indicates transactions are removed. +/// @param restoreTransactionFailed Callback method that is called each time +/// the App Store indicates transactions failed +/// to restore. +/// @param restoreCompletedTransactionsFinished Callback method that is called +/// each time the App Store +/// indicates restoring of +/// transactions has finished. +/// @param shouldAddStorePayment Callback method that is called each time an +/// in-app purchase has been initiated from the +/// App Store. +/// @param updatedDownloads Callback method that is called each time the App +/// Store indicates downloads are updated. +/// @param transactionCache An empty [FIATransactionCache] instance that is +/// responsible for keeping track of transactions that +/// arrive when not actively observing transactions. +- (instancetype)initWithQueue:(id)queue + transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated + transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved + restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed + restoreCompletedTransactionsFinished: + (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished + shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment + updatedDownloads:(nullable UpdatedDownloads)updatedDownloads + transactionCache:(nonnull id)transactionCache; + +/// Can throw exceptions if the transaction type is purchasing, should always used in a @try block. +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; + +/// Attempt to restore transactions. Require app store receipt url. +- (void)restoreTransactions:(nullable NSString *)applicationName; + +/// Displays a sheet that enables users to redeem subscription offer codes. +- (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos); + +/// Return all transactions that are not marked as complete. +- (NSArray *)getUnfinishedTransactions; + +/// This method needs to be called before any other methods. +- (void)startObservingPaymentQueue; + +/// Call this method when the Flutter app is no longer listening +- (void)stopObservingPaymentQueue; + +/// Appends a payment to the SKPaymentQueue. +/// +/// @param payment Payment object to be added to the payment queue. +/// @return whether "addPayment" was successful. +- (BOOL)addPayment:(SKPayment *)payment; + +/// Displays the price consent sheet. +/// +/// The price consent sheet is only displayed when the following +/// is true: +/// - You have increased the price of the subscription in App Store Connect. +/// - The subscriber has not yet responded to a price consent query. +/// Otherwise the method has no effect. +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos); + +@end +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h new file mode 100644 index 00000000000..40476471200 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h @@ -0,0 +1,69 @@ +// 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 + +/// A protocol that wraps SKPaymentQueue +@protocol FLTPaymentQueueProtocol + +/// An object containing the location and unique identifier of an Apple App Store storefront. +@property(nonatomic, strong) SKStorefront *storefront API_AVAILABLE(ios(13.0)); + +/// A list of SKPaymentTransactions, which each represents a single transaction +@property(nonatomic, strong) NSArray *transactions API_AVAILABLE( + ios(3.0), macos(10.7), watchos(6.2), visionos(1.0)); + +/// An object that provides information needed to complete transactions. +@property(nonatomic, weak, nullable) id delegate API_AVAILABLE( + ios(13.0), macos(10.15), watchos(6.2), visionos(1.0)); + +/// Remove a finished (i.e. failed or completed) transaction from the queue. Attempting to finish a +/// purchasing transaction will throw an exception. +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; + +/// Observers are not retained. The transactions array will only be synchronized with the server +/// while the queue has observers. This may require that the user authenticate. +- (void)addTransactionObserver:(id)observer; + +/// Add a payment to the server queue. The payment is copied to add an SKPaymentTransaction to the +/// transactions array. The same payment can be added multiple times to create multiple +/// transactions. +- (void)addPayment:(SKPayment *_Nonnull)payment; + +/// Will add completed transactions for the current user back to the queue to be re-completed. +- (void)restoreCompletedTransactions API_AVAILABLE(ios(3.0), macos(10.7), watchos(6.2), + visionos(1.0)); + +/// Will add completed transactions for the current user back to the queue to be re-completed. This +/// version requires an identifier to the user's account. +- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username + API_AVAILABLE(ios(7.0), macos(10.9), watchos(6.2), visionos(1.0)); + +/// Call this method to have StoreKit present a sheet enabling the user to redeem codes provided by +/// your app. Only for iOS. +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos); + +/// If StoreKit has called your SKPaymentQueueDelegate's "paymentQueueShouldShowPriceConsent:" +/// method and you returned NO, you can use this method to show the price consent UI at a later time +/// that is more appropriate for your app. If there is no pending price consent, this method will do +/// nothing. +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos); + +@end + +/// The default PaymentQueue that wraps SKPaymentQueue +@interface DefaultPaymentQueue : NSObject + +/// Initialize this wrapper with an SKPaymentQueue +- (instancetype)initWithQueue:(SKPaymentQueue *)queue NS_DESIGNATED_INITIALIZER; + +/// The default initializer is unavailable, as it this must be initlai +- (instancetype)init NS_UNAVAILABLE; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m new file mode 100644 index 00000000000..fd97794bae8 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m @@ -0,0 +1,74 @@ +// 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 "FLTPaymentQueueProtocol.h" + +@interface DefaultPaymentQueue () +/// The wrapped SKPaymentQueue +@property(nonatomic, strong) SKPaymentQueue *queue; +@end + +@implementation DefaultPaymentQueue + +@synthesize storefront; +@synthesize delegate; +@synthesize transactions; + +- (instancetype)initWithQueue:(SKPaymentQueue *)queue { + self = [super init]; + if (self) { + _queue = queue; + } + return self; +} + +- (void)addPayment:(SKPayment *_Nonnull)payment { + [self.queue addPayment:payment]; +} + +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction { + [self.queue finishTransaction:transaction]; +} + +- (void)addTransactionObserver:(nonnull id)observer { + [self.queue addTransactionObserver:observer]; +} + +- (void)restoreCompletedTransactions { + [self.queue restoreCompletedTransactions]; +} + +- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username { + [self.queue restoreCompletedTransactionsWithApplicationUsername:username]; +} + +- (id)delegate API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2), + visionos(1.0)) { + return self.queue.delegate; +} + +- (NSArray *)transactions API_AVAILABLE(ios(3.0), macos(10.7), watchos(6.2), + visionos(1.0)) { + return self.queue.transactions; +} + +- (SKStorefront *)storefront API_AVAILABLE(ios(13.0)) { + return self.queue.storefront; +} + +#if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos) { + [self.queue presentCodeRedemptionSheet]; +} +#endif + +#if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos) { + [self.queue showPriceConsentIfNeeded]; +} +#endif + +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h new file mode 100644 index 00000000000..d2359d4283e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +NS_ASSUME_NONNULL_BEGIN +typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response, + NSError *_Nullable errror); +/// A protocol that wraps SKRequest. +@protocol FLTRequestHandlerProtocol + +/// Wrapper for SKRequest's start +/// https://developer.apple.com/documentation/storekit/skrequest/1385534-start +- (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion; +@end +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m new file mode 100644 index 00000000000..e1714673489 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m @@ -0,0 +1,27 @@ +// 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 "FLTRequestHandlerProtocol.h" +#import +#import "FIAPRequestHandler.h" + +@interface DefaultRequestHandler () +/// The wrapped FIAPRequestHandler +@property(nonatomic, strong) FIAPRequestHandler *handler; +@end + +@implementation DefaultRequestHandler + +- (void)startProductRequestWithCompletionHandler:(nonnull ProductRequestCompletion)completion { + [self.handler startProductRequestWithCompletionHandler:completion]; +} + +- (nonnull instancetype)initWithRequestHandler:(nonnull FIAPRequestHandler *)handler { + self = [super init]; + if (self) { + _handler = handler; + } + return self; +} +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h new file mode 100644 index 00000000000..f7a58b3f2e8 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h @@ -0,0 +1,39 @@ +// 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 "FIATransactionCache.h" +#if TARGET_OS_OSX +#import +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/// A protocol that defines a cache of all transactions, both completed and in progress. +@protocol FLTTransactionCacheProtocol + +/// Adds objects to the transaction cache. +/// +/// If the cache already contains an array of objects on the specified key, the supplied +/// array will be appended to the existing array. +- (void)addObjects:(NSArray *)objects forKey:(TransactionCacheKey)key; + +/// Gets the array of objects stored at the given key. +/// +/// If there are no objects associated with the given key nil is returned. +- (NSArray *)getObjectsForKey:(TransactionCacheKey)key; + +/// Removes all objects from the transaction cache. +- (void)clear; +@end + +/// The default method channel that wraps FIATransactionCache +@interface DefaultTransactionCache : NSObject + +/// Initialize this wrapper with an FIATransactionCache +- (instancetype)initWithCache:(FIATransactionCache *)cache; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m new file mode 100644 index 00000000000..3ed268e337c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m @@ -0,0 +1,33 @@ +// 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 "FLTTransactionCacheProtocol.h" + +@interface DefaultTransactionCache () +/// The wrapped FIATransactionCache +@property(nonatomic, strong) FIATransactionCache *cache; +@end + +@implementation DefaultTransactionCache + +- (void)addObjects:(nonnull NSArray *)objects forKey:(TransactionCacheKey)key { + [self.cache addObjects:objects forKey:key]; +} + +- (void)clear { + [self.cache clear]; +} + +- (nonnull NSArray *)getObjectsForKey:(TransactionCacheKey)key { + return [self.cache getObjectsForKey:key]; +} + +- (nonnull instancetype)initWithCache:(nonnull FIATransactionCache *)cache { + self = [super init]; + if (self) { + _cache = cache; + } + return self; +} +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h new file mode 100644 index 00000000000..e44e5cf4384 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FIAObjectTranslator.h" +#import "FIAPPaymentQueueDelegate.h" +#import "FIAPReceiptManager.h" +#import "FIAPRequestHandler.h" +#import "FIAPaymentQueueHandler.h" +#import "FIATransactionCache.h" +#import "FLTMethodChannelProtocol.h" +#import "FLTPaymentQueueHandlerProtocol.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTRequestHandlerProtocol.h" +#import "FLTTransactionCacheProtocol.h" +#import "messages.g.h" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit.h new file mode 100644 index 00000000000..7abed15966e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit.h @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This has to be here b/c of this cocoapods issue - +// https://github.com/CocoaPods/CocoaPods/issues/3767 +// Without this file, the generated "in_app_purchase_storekit-Swift.h" will keep +// trying to import an "in_app_purchase_storekit.h" which doesn't exist. +#ifndef in_app_purchase_storekit_h +#define in_app_purchase_storekit_h + +#if __has_include("in_app_purchase_storekit-umbrella.h") +#import "in_app_purchase_storekit-umbrella.h" +#endif + +#endif /* in_app_purchase_storekit_h */ diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h index bc315e781cd..79bdffc76af 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h @@ -265,7 +265,7 @@ NSObject *InAppPurchaseAPIGetCodec(void); - (void)startProductRequestProductIdentifiers:(NSArray *)productIdentifiers completion:(void (^)(SKProductsResponseMessage *_Nullable, FlutterError *_Nullable))completion; -- (void)finishTransactionFinishMap:(NSDictionary *)finishMap +- (void)finishTransactionFinishMap:(NSDictionary *)finishMap error:(FlutterError *_Nullable *_Nonnull)error; - (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName error:(FlutterError *_Nullable *_Nonnull)error; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m index 9588c883b82..f013f720511 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m @@ -700,7 +700,7 @@ void SetUpInAppPurchaseAPI(id binaryMessenger, api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSDictionary *arg_finishMap = GetNullableObjectAtIndex(args, 0); + NSDictionary *arg_finishMap = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api finishTransactionFinishMap:arg_finishMap error:&error]; callback(wrapResult(nil, error)); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec index 428c612543e..fed7865a615 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec @@ -16,7 +16,8 @@ Downloaded by pub (not CocoaPods). # TODO(mvanbeusekom): update URL when in_app_purchase_storekit package is published. # Updating it before the package is published will cause a lint error and block the tree. s.documentation_url = 'https://pub.dev/packages/in_app_purchase' - s.source_files = 'Classes/**/*' + s.swift_version = '5.0' + s.source_files = 'Classes/**/*.{h,m,swift}' s.public_header_files = 'Classes/**/*.h' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' @@ -24,4 +25,8 @@ Downloaded by pub (not CocoaPods). s.osx.deployment_target = '10.15' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.resource_bundles = {'in_app_purchase_storekit_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile index 035842459e6..bb776acdd2c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile @@ -33,8 +33,6 @@ target 'Runner' do target 'RunnerTests' do inherit! :search_paths - # Matches in_app_purchase test_spec dependency. - pod 'OCMock', '~> 3.6' end end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 06e0b3ac947..7d99ab6109b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,23 +7,24 @@ objects = { /* Begin PBXBuildFile section */ - 0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1630769A874F9381BC761FE1 /* libPods-Runner.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 688DE35021F2A5A100EA2684 /* TranslatorTests.m */; }; - 6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */; }; - 6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34B21EEB4B800D37AEF /* Stubs.m */; }; - 7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; }; - A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */; }; - F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */; }; - F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */; }; - F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3132342BC89008449C7 /* PaymentQueueTests.m */; }; + C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */; }; + E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */; }; + F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */; }; + F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD392C1256DD0067C78A /* Stubs.m */; }; + F295AD412C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */; }; + F295AD422C1256F50067C78A /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */; }; + F295AD432C1256F50067C78A /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */; }; + F295AD442C1256F50067C78A /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */; }; + F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */; }; + F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD402C1256F50067C78A /* TranslatorTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -50,20 +51,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1630769A874F9381BC761FE1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 688DE35021F2A5A100EA2684 /* TranslatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TranslatorTests.m; sourceTree = ""; }; - 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProductRequestHandlerTests.m; sourceTree = ""; }; - 6896B34A21EEB4B800D37AEF /* Stubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stubs.h; sourceTree = ""; }; - 6896B34B21EEB4B800D37AEF /* Stubs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Stubs.m; sourceTree = ""; }; + 6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 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 = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -72,16 +69,21 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A5279297219369C600FF69E6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; A59001A421E69658004A3E5E /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = ""; }; - A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; - F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; sourceTree = ""; }; + AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; + F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStubs.swift; sourceTree = ""; }; + F295AD362C1251300067C78A /* Stubs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Stubs.h; path = ../../shared/RunnerTests/Stubs.h; sourceTree = ""; }; + F295AD392C1256DD0067C78A /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = ""; }; + F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIAPPaymentQueueDeleteTests.m; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; + F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = InAppPurchasePluginTests.m; path = ../../shared/RunnerTests/InAppPurchasePluginTests.m; sourceTree = ""; }; + F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProductRequestHandlerTests.m; path = ../../shared/RunnerTests/ProductRequestHandlerTests.m; sourceTree = ""; }; + F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIATransactionCacheTests.m; path = ../../shared/RunnerTests/FIATransactionCacheTests.m; sourceTree = ""; }; + F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PaymentQueueTests.m; path = ../../shared/RunnerTests/PaymentQueueTests.m; sourceTree = ""; }; + F295AD402C1256F50067C78A /* TranslatorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TranslatorTests.m; path = ../../shared/RunnerTests/TranslatorTests.m; sourceTree = ""; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; - F78AF3132342BC89008449C7 /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaymentQueueTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -90,7 +92,7 @@ buildActionMask = 2147483647; files = ( A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */, - 0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */, + E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -98,7 +100,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */, + C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -108,10 +110,10 @@ 0B4403AC68C3196AECF5EF89 /* Pods */ = { isa = PBXGroup; children = ( - E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */, - 2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */, - 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */, - 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */, + 6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */, + CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */, + 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */, + 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -184,15 +186,16 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( - A59001A821E69658004A3E5E /* Info.plist */, - 6896B34A21EEB4B800D37AEF /* Stubs.h */, - 6896B34B21EEB4B800D37AEF /* Stubs.m */, - A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */, - 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */, - F78AF3132342BC89008449C7 /* PaymentQueueTests.m */, - 688DE35021F2A5A100EA2684 /* TranslatorTests.m */, - F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */, - F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */, + F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */, + F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */, + F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */, + F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */, + F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */, + F295AD402C1256F50067C78A /* TranslatorTests.m */, + F295AD392C1256DD0067C78A /* Stubs.m */, + F295AD362C1251300067C78A /* Stubs.h */, + F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, + F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, ); path = RunnerTests; sourceTree = ""; @@ -201,8 +204,8 @@ isa = PBXGroup; children = ( A5279297219369C600FF69E6 /* StoreKit.framework */, - 1630769A874F9381BC761FE1 /* libPods-Runner.a */, - 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */, + 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */, + AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; @@ -214,14 +217,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */, + 9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */, + 325E900B3895C722B0E09318 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -236,7 +239,7 @@ isa = PBXNativeTarget; buildConfigurationList = A59001AD21E69658004A3E5E /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */, + 39A4BCA317070A14A6C5C70F /* [CP] Check Pods Manifest.lock */, A59001A021E69658004A3E5E /* Sources */, A59001A121E69658004A3E5E /* Frameworks */, A59001A221E69658004A3E5E /* Resources */, @@ -271,6 +274,7 @@ }; A59001A321E69658004A3E5E = { CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1530; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -317,23 +321,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */ = { + 325E900B3895C722B0E09318 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -341,17 +329,19 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/in_app_purchase_storekit/in_app_purchase_storekit_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/shared_preferences_foundation/shared_preferences_foundation_privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/in_app_purchase_storekit_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/shared_preferences_foundation_privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */ = { + 39A4BCA317070A14A6C5C70F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -373,6 +363,22 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -388,7 +394,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */ = { + 9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -427,13 +433,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */, - F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */, - 6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */, - 688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */, - F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */, - A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */, - 6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */, + F295AD432C1256F50067C78A /* ProductRequestHandlerTests.m in Sources */, + F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, + F295AD412C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m in Sources */, + F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */, + F295AD442C1256F50067C78A /* FIATransactionCacheTests.m in Sources */, + F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */, + F295AD422C1256F50067C78A /* InAppPurchasePluginTests.m in Sources */, + F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -629,10 +636,11 @@ }; A59001AB21E69658004A3E5E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -650,16 +658,20 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; A59001AC21E69658004A3E5E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -676,6 +688,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit index 29ebbebf553..58f3d4304fc 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit @@ -1,4 +1,14 @@ { + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, "identifier" : "6073E9A3", "nonRenewingSubscriptions" : [ @@ -118,7 +128,10 @@ "recurringSubscriptionPeriod" : "P1W", "referenceName" : "subscription_silver", "subscriptionGroupID" : "D0FEE8D8", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] }, { "adHocOffers" : [ @@ -143,13 +156,16 @@ "recurringSubscriptionPeriod" : "P1M", "referenceName" : "subscription_gold", "subscriptionGroupID" : "D0FEE8D8", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] } ] } ], "version" : { - "major" : 3, + "major" : 4, "minor" : 0 } } diff --git a/packages/platform/example/ios/Runner/Runner-Bridging-Header.h b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/RunnerTests-Bridging-Header.h similarity index 81% rename from packages/platform/example/ios/Runner/Runner-Bridging-Header.h rename to packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index eb7e8ba8052..f85b226b93f 100644 --- a/packages/platform/example/ios/Runner/Runner-Bridging-Header.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -2,4 +2,4 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "GeneratedPluginRegistrant.h" +#import "Stubs.h" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/SwiftStubs.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/SwiftStubs.swift new file mode 100644 index 00000000000..b61b83b229c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/SwiftStubs.swift @@ -0,0 +1,26 @@ +// 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 Foundation +import StoreKitTest + +@testable import in_app_purchase_storekit + +class InAppPurchasePluginStub: InAppPurchasePlugin { + override func getProductRequest(withIdentifiers productIdentifiers: Set) + -> SKProductsRequest + { + return SKProductRequestStub.init(productIdentifiers: productIdentifiers) + } + + override func getProduct(productID: String) -> SKProduct? { + if productID == "" { + return nil + } + return SKProductStub.init(productID: productID) + } + override func getRefreshReceiptRequest(properties: [String: Any]?) -> SKReceiptRefreshRequest { + return SKReceiptRefreshRequest(receiptProperties: properties) + } +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile index 04238b6a5f2..0fcebfff858 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile @@ -34,8 +34,6 @@ target 'Runner' do target 'RunnerTests' do inherit! :search_paths - - pod 'OCMock', '~> 3.6' end end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 76173704741..86bb21175b1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -26,8 +26,9 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - A2C6CD5797E6A6721FDBCA1C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36DEEA66738F64D983F76848 /* Pods_Runner.framework */; }; - C51E64432925727D7AC7BBFF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE8A421F08C80BE6E90142D5 /* Pods_RunnerTests.framework */; }; + 45146735C2BCBA6C4526CAA0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73F6308C9AC0BA52F286AE52 /* Pods_Runner.framework */; }; + 4B50788839EDAFEFFC4A752E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A207547725A30AEC178E886 /* Pods_RunnerTests.framework */; }; + F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; F79BDC102905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC0F2905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m */; }; F79BDC122905FBF700E3999D /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC112905FBF700E3999D /* FIATransactionCacheTests.m */; }; F79BDC142905FBFE00E3999D /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC132905FBFE00E3999D /* InAppPurchasePluginTests.m */; }; @@ -68,6 +69,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1A207547725A30AEC178E886 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -81,16 +83,17 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 36DEEA66738F64D983F76848 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 4E423AE82F466005587C3567 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 5E5D46173E3025B0DB32A1BE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 5EBC5A8BA44B08330BA605AB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 62F1680C5AE033907C1DF7AB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7172FBE7DF73E41E9FC6E6D7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 73F6308C9AC0BA52F286AE52 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 79C769808042591E28A245B8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 89C4EE02AA38CF7BF853991B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 8D6AA81A407E58E8954F145B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - 9A4FEABF1DEF0D106FEB7974 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - B6C8FD76BB3278AA51FED870 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - EE8A421F08C80BE6E90142D5 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BEE5894B3A0A82FD8D495BDD /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + DAB7E6DD38EB6FA87E605270 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; + F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; F700DD0228E652A10004836B /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F79BDC0F2905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIAPPaymentQueueDeleteTests.m; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; F79BDC112905FBF700E3999D /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIATransactionCacheTests.m; path = ../../shared/RunnerTests/FIATransactionCacheTests.m; sourceTree = ""; }; @@ -108,7 +111,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A2C6CD5797E6A6721FDBCA1C /* Pods_Runner.framework in Frameworks */, + 45146735C2BCBA6C4526CAA0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -116,7 +119,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C51E64432925727D7AC7BBFF /* Pods_RunnerTests.framework in Frameworks */, + 4B50788839EDAFEFFC4A752E /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -126,12 +129,12 @@ 09D47623A8E19B84FF0453EE /* Pods */ = { isa = PBXGroup; children = ( - B6C8FD76BB3278AA51FED870 /* Pods-Runner.debug.xcconfig */, - 9A4FEABF1DEF0D106FEB7974 /* Pods-Runner.release.xcconfig */, - 62F1680C5AE033907C1DF7AB /* Pods-Runner.profile.xcconfig */, - 5E5D46173E3025B0DB32A1BE /* Pods-RunnerTests.debug.xcconfig */, - 5EBC5A8BA44B08330BA605AB /* Pods-RunnerTests.release.xcconfig */, - 4E423AE82F466005587C3567 /* Pods-RunnerTests.profile.xcconfig */, + 8D6AA81A407E58E8954F145B /* Pods-Runner.debug.xcconfig */, + 7172FBE7DF73E41E9FC6E6D7 /* Pods-Runner.release.xcconfig */, + DAB7E6DD38EB6FA87E605270 /* Pods-Runner.profile.xcconfig */, + 89C4EE02AA38CF7BF853991B /* Pods-RunnerTests.debug.xcconfig */, + 79C769808042591E28A245B8 /* Pods-RunnerTests.release.xcconfig */, + BEE5894B3A0A82FD8D495BDD /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -153,8 +156,8 @@ 33CEB47122A05771004F2AC0 /* Flutter */, F700DD0328E652A10004836B /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, 09D47623A8E19B84FF0453EE /* Pods */, + E89234C8EA67B35E702D54F6 /* Frameworks */, ); sourceTree = ""; }; @@ -202,11 +205,11 @@ path = Runner; sourceTree = ""; }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { + E89234C8EA67B35E702D54F6 /* Frameworks */ = { isa = PBXGroup; children = ( - 36DEEA66738F64D983F76848 /* Pods_Runner.framework */, - EE8A421F08C80BE6E90142D5 /* Pods_RunnerTests.framework */, + 73F6308C9AC0BA52F286AE52 /* Pods_Runner.framework */, + 1A207547725A30AEC178E886 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -223,6 +226,8 @@ F79BDC1D2905FC3900E3999D /* TranslatorTests.m */, F79BDC192905FC1F00E3999D /* ProductRequestHandlerTests.m */, F79BDC112905FBF700E3999D /* FIATransactionCacheTests.m */, + F2C3A7402BD9D33D000D35F2 /* Stubs.swift */, + F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */, ); path = RunnerTests; sourceTree = ""; @@ -234,13 +239,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 377E3E3C5CA24E98C4B6A4BB /* [CP] Check Pods Manifest.lock */, + F83C62E1BF4D0A86747FA7CF /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 23A80E9A6DAA80757416464A /* [CP] Embed Pods Frameworks */, + 2C0BDBFDB384E45F10121440 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -256,11 +261,10 @@ isa = PBXNativeTarget; buildConfigurationList = F700DD0B28E652A10004836B /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 959FA4942EA5DA018C52D3DA /* [CP] Check Pods Manifest.lock */, + 75DE29BFD3B3C1D676C22160 /* [CP] Check Pods Manifest.lock */, F700DCFE28E652A10004836B /* Sources */, F700DCFF28E652A10004836B /* Frameworks */, F700DD0028E652A10004836B /* Resources */, - 1FAA0D39365CA43DED71E657 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -279,7 +283,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1400; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -298,7 +302,7 @@ }; F700DD0128E652A10004836B = { CreatedOnToolsVersion = 14.0.1; - LastSwiftMigration = 1400; + LastSwiftMigration = 1530; TestTargetID = 33CC10EC2044A3C60003C045; }; }; @@ -343,24 +347,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1FAA0D39365CA43DED71E657 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 23A80E9A6DAA80757416464A /* [CP] Embed Pods Frameworks */ = { + 2C0BDBFDB384E45F10121440 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -415,7 +402,7 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 377E3E3C5CA24E98C4B6A4BB /* [CP] Check Pods Manifest.lock */ = { + 75DE29BFD3B3C1D676C22160 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -430,14 +417,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 959FA4942EA5DA018C52D3DA /* [CP] Check Pods Manifest.lock */ = { + F83C62E1BF4D0A86747FA7CF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -452,7 +439,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -483,6 +470,7 @@ F79BDC102905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m in Sources */, F79BDC142905FBFE00E3999D /* InAppPurchasePluginTests.m in Sources */, F79BDC122905FBF700E3999D /* FIATransactionCacheTests.m in Sources */, + F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -746,7 +734,7 @@ }; F700DD0828E652A10004836B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5E5D46173E3025B0DB32A1BE /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 89C4EE02AA38CF7BF853991B /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -767,17 +755,19 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/in_app_purchase_storekit/in_app_purchase_storekit.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/integration_test/integration_test.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/shared_preferences_macos/shared_preferences_macos.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Debug; }; F700DD0928E652A10004836B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5EBC5A8BA44B08330BA605AB /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 79C769808042591E28A245B8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -797,17 +787,18 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/in_app_purchase_storekit/in_app_purchase_storekit.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/integration_test/integration_test.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/shared_preferences_macos/shared_preferences_macos.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Release; }; F700DD0A28E652A10004836B /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4E423AE82F466005587C3567 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = BEE5894B3A0A82FD8D495BDD /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -827,10 +818,11 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/in_app_purchase_storekit/in_app_purchase_storekit.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/integration_test/integration_test.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/shared_preferences_macos/shared_preferences_macos.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RunnerTests/RunnerTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Profile; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index dd063527309..5eb222feadb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + skipped = "NO"> + + + + diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/DebugProfile.entitlements b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..25a95d1ac5c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/DebugProfile.entitlements +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + < + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Release.entitlements b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Release.entitlements +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/dynamic_layouts/example/ios/Runner/Runner-Bridging-Header.h b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/RunnerTests-Bridging-Header.h similarity index 81% rename from packages/dynamic_layouts/example/ios/Runner/Runner-Bridging-Header.h rename to packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/RunnerTests-Bridging-Header.h index be4faecf516..f85b226b93f 100644 --- a/packages/dynamic_layouts/example/ios/Runner/Runner-Bridging-Header.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/RunnerTests-Bridging-Header.h @@ -1,4 +1,5 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "GeneratedPluginRegistrant.h" + +#import "Stubs.h" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/Stubs.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/Stubs.swift new file mode 100644 index 00000000000..b61b83b229c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/Stubs.swift @@ -0,0 +1,26 @@ +// 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 Foundation +import StoreKitTest + +@testable import in_app_purchase_storekit + +class InAppPurchasePluginStub: InAppPurchasePlugin { + override func getProductRequest(withIdentifiers productIdentifiers: Set) + -> SKProductsRequest + { + return SKProductRequestStub.init(productIdentifiers: productIdentifiers) + } + + override func getProduct(productID: String) -> SKProduct? { + if productID == "" { + return nil + } + return SKProductStub.init(productID: productID) + } + override func getRefreshReceiptRequest(properties: [String: Any]?) -> SKReceiptRefreshRequest { + return SKReceiptRefreshRequest(receiptProperties: properties) + } +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m index 187cc6e37bf..1056c343b17 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import "FIAObjectTranslator.h" #import "FIAPaymentQueueHandler.h" @@ -14,17 +13,14 @@ API_UNAVAILABLE(tvos, macos, watchos) @interface FIAPPaymentQueueDelegateTests : XCTestCase -@property(strong, nonatomic) FlutterMethodChannel *channel; -@property(strong, nonatomic) SKPaymentTransaction *transaction; -@property(strong, nonatomic) SKStorefront *storefront; +@property(nonatomic, strong) SKPaymentTransaction *transaction; +@property(nonatomic, strong) SKStorefront *storefront; @end @implementation FIAPPaymentQueueDelegateTests - (void)setUp { - self.channel = OCMClassMock(FlutterMethodChannel.class); - NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @"transactionState" : @(SKPaymentTransactionStatePurchasing), @@ -45,21 +41,24 @@ - (void)setUp { } - (void)tearDown { - self.channel = nil; } - (void)testShouldContinueTransaction { if (@available(iOS 13.0, *)) { - FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldContinueTransaction"); + XCTAssertEqualObjects(arguments, + [FIAObjectTranslator getMapFromSKStorefront:self.storefront + andSKPaymentTransaction:self.transaction]); + result(@NO); + }; - OCMStub([self.channel - invokeMethod:@"shouldContinueTransaction" - arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront - andSKPaymentTransaction:self.transaction] - result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]); + FIAPPaymentQueueDelegate *delegate = + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class) + BOOL shouldContinue = [delegate paymentQueue:[[SKPaymentQueueStub alloc] init] shouldContinueTransaction:self.transaction inStorefront:self.storefront]; @@ -69,15 +68,19 @@ - (void)testShouldContinueTransaction { - (void)testShouldContinueTransaction_should_default_to_yes { if (@available(iOS 13.0, *)) { + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - OCMStub([self.channel invokeMethod:@"shouldContinueTransaction" - arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront - andSKPaymentTransaction:self.transaction] - result:[OCMArg any]]); + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldContinueTransaction"); + XCTAssertEqualObjects(arguments, + [FIAObjectTranslator getMapFromSKStorefront:self.storefront + andSKPaymentTransaction:self.transaction]); + }; - BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class) + BOOL shouldContinue = [delegate paymentQueue:[[SKPaymentQueueStub alloc] init] shouldContinueTransaction:self.transaction inStorefront:self.storefront]; @@ -88,16 +91,19 @@ - (void)testShouldContinueTransaction_should_default_to_yes { #if TARGET_OS_IOS - (void)testShouldShowPriceConsentIfNeeded { if (@available(iOS 13.4, *)) { + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - OCMStub([self.channel - invokeMethod:@"shouldShowPriceConsent" - arguments:nil - result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]); + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldShowPriceConsent"); + XCTAssertNil(arguments); + result(@NO); + }; BOOL shouldShow = - [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)]; + [delegate paymentQueueShouldShowPriceConsent:[[SKPaymentQueueStub alloc] init]]; XCTAssertFalse(shouldShow); } @@ -107,15 +113,18 @@ - (void)testShouldShowPriceConsentIfNeeded { #if TARGET_OS_IOS - (void)testShouldShowPriceConsentIfNeeded_should_default_to_yes { if (@available(iOS 13.4, *)) { + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - OCMStub([self.channel invokeMethod:@"shouldShowPriceConsent" - arguments:nil - result:[OCMArg any]]); + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldShowPriceConsent"); + XCTAssertNil(arguments); + }; BOOL shouldShow = - [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)]; + [delegate paymentQueueShouldShowPriceConsent:[[SKPaymentQueueStub alloc] init]]; XCTAssertTrue(shouldShow); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 905903df056..1820ff8f65a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -2,18 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import "FIAPaymentQueueHandler.h" -#import "InAppPurchasePlugin+TestOnly.h" +#import "RunnerTests-Swift.h" #import "Stubs.h" @import in_app_purchase_storekit; @interface InAppPurchasePluginTest : XCTestCase -@property(strong, nonatomic) FIAPReceiptManagerStub *receiptManagerStub; -@property(strong, nonatomic) InAppPurchasePlugin *plugin; +@property(nonatomic, strong) FIAPReceiptManagerStub *receiptManagerStub; +@property(nonatomic, strong) InAppPurchasePlugin *plugin; @end @@ -21,7 +20,12 @@ @implementation InAppPurchasePluginTest - (void)setUp { self.receiptManagerStub = [FIAPReceiptManagerStub new]; - self.plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub]; + self.plugin = [[InAppPurchasePluginStub alloc] + initWithReceiptManager:self.receiptManagerStub + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; + }]; } - (void)tearDown { @@ -36,23 +40,23 @@ - (void)testCanMakePayments { - (void)testPaymentQueueStorefront { if (@available(iOS 13, macOS 10.15, *)) { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); NSDictionary *storefrontMap = @{ @"countryCode" : @"USA", @"identifier" : @"unique_identifier", }; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; - OCMStub(mockQueue.storefront).andReturn([[SKStorefrontStub alloc] initWithMap:storefrontMap]); + queueStub.storefront = [[SKStorefrontStub alloc] initWithMap:storefrontMap]; - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; FlutterError *error; SKStorefrontMessage *result = [self.plugin storefrontWithError:&error]; @@ -67,19 +71,17 @@ - (void)testPaymentQueueStorefront { - (void)testPaymentQueueStorefrontReturnsNil { if (@available(iOS 13, macOS 10.15, *)) { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); - - OCMStub(mockQueue.storefront).andReturn(nil); - - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; + + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; FlutterError *error; SKStorefrontMessage *resultMap = [self.plugin storefrontWithError:&error]; @@ -125,14 +127,23 @@ - (void)testFinishTransactionSucceeds { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - SKPaymentTransactionStub *paymentTransaction = + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = @[ paymentTransaction ]; + NSArray *array = @[ paymentTransactionStub ]; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; + queue.transactions = array; - self.plugin.paymentQueueHandler = mockHandler; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; + + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; FlutterError *error; [self.plugin finishTransactionFinishMap:args error:&error]; @@ -163,13 +174,23 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - SKPaymentTransactionStub *paymentTransaction = + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[ paymentTransaction ]); + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + queueStub.transactions = @[ paymentTransactionStub ]; + + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; - self.plugin.paymentQueueHandler = mockHandler; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; + ; FlutterError *error; [self.plugin finishTransactionFinishMap:args error:&error]; @@ -182,20 +203,21 @@ - (void)testGetProductResponseWithRequestError { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] - initWithReceiptManager:nil - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + initWithReceiptManager:_receiptManagerStub + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; NSError *error = [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error, - nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, error); + }; [plugin startProductRequestProductIdentifiers:argument @@ -216,28 +238,23 @@ - (void)testGetProductResponseWithNoResponse { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); - + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] - initWithReceiptManager:nil - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + initWithReceiptManager:_receiptManagerStub + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; - NSError *error = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - [NSNull null], nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, nil); + }; [plugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { [expectation fulfill]; - XCTAssertNotNil(error); XCTAssertNotNil(startProductRequestError); XCTAssertEqualObjects(startProductRequestError.code, @"storekit_platform_no_response"); @@ -252,15 +269,20 @@ - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { @"simulatesAskToBuyInSandbox" : @YES, }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(NO); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; FlutterError *error; + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + addPaymentInvokeCount += 1; + return NO; + }; + [self.plugin addPaymentPaymentMap:argument error:&error]; - OCMVerify(times(1), [mockHandler addPayment:[OCMArg any]]); + XCTAssertEqual(addPaymentInvokeCount, 1); XCTAssertEqualObjects(@"storekit_duplicate_product_object", error.code); XCTAssertEqualObjects(@"There is a pending transaction for the same product identifier. " @"Please either wait for it to be finished or finish it manually " @@ -297,21 +319,24 @@ - (void)testAddPaymentSuccessWithoutPaymentDiscount { @"simulatesAskToBuyInSandbox" : @YES, }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + XCTAssert(payment != nil); + XCTAssertEqual(payment.productIdentifier, @"123"); + XCTAssert(payment.quantity == 1); + addPaymentInvokeCount++; + return YES; + }; + FlutterError *error; [self.plugin addPaymentPaymentMap:argument error:&error]; XCTAssertNil(error); - OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - XCTAssert(payment != nil); - XCTAssertEqual(payment.productIdentifier, @"123"); - XCTAssert(payment.quantity == 1); - return YES; - }]]); + XCTAssertEqual(addPaymentInvokeCount, 1); } - (void)testAddPaymentSuccessWithPaymentDiscount { @@ -328,43 +353,42 @@ - (void)testAddPaymentSuccessWithPaymentDiscount { } }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + if (@available(iOS 12.2, *)) { + SKPaymentDiscount *discount = payment.paymentDiscount; + XCTAssertEqual(discount.identifier, @"test_identifier"); + XCTAssertEqual(discount.keyIdentifier, @"test_key_identifier"); + XCTAssertEqualObjects( + discount.nonce, + [[NSUUID alloc] initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]); + XCTAssertEqual(discount.signature, @"test_signature"); + addPaymentInvokeCount++; + return YES; + } + addPaymentInvokeCount++; + return YES; + }; FlutterError *error; [self.plugin addPaymentPaymentMap:argument error:&error]; + XCTAssertEqual(addPaymentInvokeCount, 1); XCTAssertNil(error); - OCMVerify( - times(1), - [mockHandler - addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - if (@available(iOS 12.2, *)) { - SKPaymentDiscount *discount = payment.paymentDiscount; - - return [discount.identifier isEqual:@"test_identifier"] && - [discount.keyIdentifier isEqual:@"test_key_identifier"] && - [discount.nonce - isEqual:[[NSUUID alloc] - initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]] && - [discount.signature isEqual:@"test_signature"] && - [discount.timestamp isEqual:@(1635847102)]; - } - - return YES; - }]]); } - (void)testAddPaymentFailureWithInvalidPaymentDiscount { // Support for payment discount is only available on iOS 12.2 and higher. if (@available(iOS 12.2, *)) { - NSDictionary *argument = @{ + NSDictionary *invalidDiscount = @{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, @"paymentDiscount" : @{ + /// This payment discount is missing the field `identifier`, and is thus malformed @"keyIdentifier" : @"test_key_identifier", @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003", @"signature" : @"test_signature", @@ -372,25 +396,26 @@ - (void)testAddPaymentFailureWithInvalidPaymentDiscount { } }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - id translator = OCMClassMock(FIAObjectTranslator.class); + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + + __block NSInteger addPaymentCount = 0; + handlerStub.addPaymentStub = ^BOOL(SKPayment *_Nonnull payment) { + addPaymentCount++; + return YES; + }; - NSString *errorMsg = @"Some error occurred"; - OCMStub(ClassMethod([translator - getSKPaymentDiscountFromMap:[OCMArg any] - withError:(NSString __autoreleasing **)[OCMArg setTo:errorMsg]])) - .andReturn(nil); - self.plugin.paymentQueueHandler = mockHandler; + self.plugin.paymentQueueHandler = handlerStub; FlutterError *error; - [self.plugin addPaymentPaymentMap:argument error:&error]; + [self.plugin addPaymentPaymentMap:invalidDiscount error:&error]; XCTAssertEqualObjects(@"storekit_invalid_payment_discount_object", error.code); XCTAssertEqualObjects(@"You have requested a payment and specified a " - @"payment discount with invalid properties. Some error occurred", + @"payment discount with invalid properties. When specifying a payment " + @"discount the 'identifier' field is mandatory.", error.message); - XCTAssertEqualObjects(argument, error.details); - OCMVerify(never(), [mockHandler addPayment:[OCMArg any]]); + XCTAssertEqualObjects(invalidDiscount, error.details); + XCTAssertEqual(0, addPaymentCount); } } @@ -401,27 +426,30 @@ - (void)testAddPaymentWithNullSandboxArgument { @"simulatesAskToBuyInSandbox" : [NSNull null], }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; FlutterError *error; + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + XCTAssertEqual(payment.simulatesAskToBuyInSandbox, false); + addPaymentInvokeCount++; + return YES; + }; + [self.plugin addPaymentPaymentMap:argument error:&error]; - OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - return !payment.simulatesAskToBuyInSandbox; - }]]); + XCTAssertEqual(addPaymentInvokeCount, 1); } - (void)testRestoreTransactions { XCTestExpectation *expectation = [self expectationWithDescription:@"result successfully restore transactions"]; - SKPaymentQueueStub *queue = [SKPaymentQueueStub new]; - queue.testState = SKPaymentTransactionStatePurchased; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; __block BOOL callbackInvoked = NO; - self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub transactionsUpdated:^(NSArray *_Nonnull transactions) { } transactionRemoved:nil @@ -432,8 +460,8 @@ - (void)testRestoreTransactions { } shouldAddStorePayment:nil updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - [queue addTransactionObserver:self.plugin.paymentQueueHandler]; + transactionCache:cacheStub]; + [queueStub addTransactionObserver:self.plugin.paymentQueueHandler]; FlutterError *error; [self.plugin restoreTransactionsApplicationUserName:nil error:&error]; @@ -450,8 +478,8 @@ - (void)testRetrieveReceiptDataSuccess { } - (void)testRetrieveReceiptDataNil { - NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); - OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); + self.receiptManagerStub.returnNilURL = YES; + FlutterError *error; NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; XCTAssertNil(result); @@ -475,10 +503,27 @@ - (void)testRetrieveReceiptDataError { - (void)testRefreshReceiptRequest { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - [self.plugin refreshReceiptReceiptProperties:nil - completion:^(FlutterError *_Nullable error) { - [expectation fulfill]; - }]; + + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:_receiptManagerStub + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; + }]; + + NSError *recieptError = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, recieptError); + }; + + [plugin refreshReceiptReceiptProperties:nil + completion:^(FlutterError *_Nullable error) { + [expectation fulfill]; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; } @@ -491,10 +536,28 @@ - (void)testRefreshReceiptRequestWithParams { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - [self.plugin refreshReceiptReceiptProperties:properties - completion:^(FlutterError *_Nullable error) { - [expectation fulfill]; - }]; + + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:_receiptManagerStub + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; + }]; + + NSError *recieptError = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, recieptError); + }; + + [plugin refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; } @@ -507,20 +570,21 @@ - (void)testRefreshReceiptRequestWithError { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] - initWithReceiptManager:nil - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + initWithReceiptManager:_receiptManagerStub + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; NSError *recieptError = [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - recieptError, nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, recieptError); + }; [plugin refreshReceiptReceiptProperties:properties completion:^(FlutterError *_Nullable error) { @@ -535,18 +599,24 @@ - (void)testRefreshReceiptRequestWithError { /// presentCodeRedemptionSheetWithError:error is only available on iOS #if TARGET_OS_IOS - (void)testPresentCodeRedemptionSheet { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger presentCodeRedemptionSheetCount = 0; + handlerStub.presentCodeRedemptionSheetStub = ^{ + presentCodeRedemptionSheetCount++; + }; FlutterError *error; [self.plugin presentCodeRedemptionSheetWithError:&error]; - OCMVerify(times(1), [mockHandler presentCodeRedemptionSheet]); + XCTAssertEqual(1, presentCodeRedemptionSheetCount); } #endif - (void)testGetPendingTransactions { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @"transactionState" : @(SKPaymentTransactionStatePurchasing), @@ -557,18 +627,15 @@ - (void)testGetPendingTransactions { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), @"originalTransaction" : [NSNull null], }; - OCMStub(mockQueue.transactions).andReturn(@[ [[SKPaymentTransactionStub alloc] - initWithMap:transactionMap] ]); - - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + queueStub.transactions = @[ [[SKPaymentTransactionStub alloc] initWithMap:transactionMap] ]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; FlutterError *error; SKPaymentTransactionStub *original = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; @@ -582,37 +649,50 @@ - (void)testGetPendingTransactions { } - (void)testStartObservingPaymentQueue { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger startObservingCount = 0; + handlerStub.startObservingPaymentQueueStub = ^{ + startObservingCount++; + }; FlutterError *error; [self.plugin startObservingPaymentQueueWithError:&error]; - OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); + XCTAssertEqual(1, startObservingCount); } - (void)testStopObservingPaymentQueue { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger stopObservingCount = 0; + handlerStub.stopObservingPaymentQueueStub = ^{ + stopObservingCount++; + }; FlutterError *error; [self.plugin stopObservingPaymentQueueWithError:&error]; - OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]); + XCTAssertEqual(1, stopObservingCount); } #if TARGET_OS_IOS - (void)testRegisterPaymentQueueDelegate { + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; if (@available(iOS 13, *)) { - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; + + self.plugin.registrar = [[FlutterPluginRegistrarStub alloc] init]; // Verify the delegate is nil before we register one. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); @@ -624,31 +704,38 @@ - (void)testRegisterPaymentQueueDelegate { XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); } } -#endif - (void)testRemovePaymentQueueDelegate { if (@available(iOS 13, *)) { - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - self.plugin.paymentQueueHandler.delegate = OCMProtocolMock(@protocol(SKPaymentQueueDelegate)); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; + + self.plugin.registrar = [[FlutterPluginRegistrarStub alloc] init]; + + // Verify the delegate is nil before we register one. + XCTAssertNil(self.plugin.paymentQueueHandler.delegate); + + FlutterError *error; + [self.plugin registerPaymentQueueDelegateWithError:&error]; // Verify the delegate is not nil before removing it. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); - FlutterError *error; [self.plugin removePaymentQueueDelegateWithError:&error]; // Verify the delegate is nill after removing it. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); } } +#endif - (void)testHandleTransactionsUpdated { NSDictionary *transactionMap = @{ @@ -661,19 +748,32 @@ - (void)testHandleTransactionsUpdated { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] + initWithReceiptManager:self.receiptManagerStub + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; + }]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; - SKPaymentTransactionStub *paymentTransaction = + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"updatedTransactions", method); + XCTAssertNotNil(arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; + + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSArray *array = [NSArray arrayWithObjects:paymentTransactionStub, nil]; NSMutableArray *maps = [NSMutableArray new]; - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransactionStub]]; [plugin handleTransactionsUpdated:array]; - OCMVerify(times(1), [mockChannel invokeMethod:@"updatedTransactions" arguments:[OCMArg any]]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testHandleTransactionsRemoved { @@ -687,42 +787,78 @@ - (void)testHandleTransactionsRemoved { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - SKPaymentTransactionStub *paymentTransaction = + InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] + initWithReceiptManager:self.receiptManagerStub + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; + }]; + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSArray *array = [NSArray arrayWithObjects:paymentTransactionStub, nil]; NSMutableArray *maps = [NSMutableArray new]; - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransactionStub]]; + + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; + + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"removedTransactions", method); + XCTAssertEqualObjects(maps, arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; [plugin handleTransactionsRemoved:array]; - OCMVerify(times(1), [mockChannel invokeMethod:@"removedTransactions" arguments:maps]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testHandleTransactionRestoreFailed { - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] + initWithReceiptManager:self.receiptManagerStub + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; + }]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; + NSError *error = [NSError errorWithDomain:@"error" code:0 userInfo:nil]; + + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"restoreCompletedTransactionsFailed", method); + XCTAssertEqualObjects([FIAObjectTranslator getMapFromNSError:error], arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; - NSError *error; [plugin handleTransactionRestoreFailed:error]; - OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" - arguments:[FIAObjectTranslator getMapFromNSError:error]]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testRestoreCompletedTransactionsFinished { - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] + initWithReceiptManager:self.receiptManagerStub + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; + }]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"paymentQueueRestoreCompletedTransactionsFinished", method); + XCTAssertNil(arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; [plugin restoreCompletedTransactionsFinished]; - OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" - arguments:nil]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testShouldAddStorePayment { @@ -743,37 +879,65 @@ - (void)testShouldAddStorePayment { }; SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; - SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap]; + SKProductStub *productStub = [[SKProductStub alloc] initWithMap:productMap]; - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] + initWithReceiptManager:self.receiptManagerStub + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; + }]; NSDictionary *args = @{ @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], - @"product" : [FIAObjectTranslator getMapFromSKProduct:product] + @"product" : [FIAObjectTranslator getMapFromSKProduct:productStub] }; - BOOL result = [plugin shouldAddStorePayment:payment product:product]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + + __block NSInteger invokeMethodCount = 0; + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"shouldAddStorePayment", method); + XCTAssertEqualObjects(args, arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; + + BOOL result = [plugin shouldAddStorePaymentWithPayment:payment product:productStub]; XCTAssertEqual(result, NO); - OCMVerify(times(1), [mockChannel invokeMethod:@"shouldAddStorePayment" arguments:args]); + XCTAssertEqual(invokeMethodCount, 1); } #if TARGET_OS_IOS - (void)testShowPriceConsentIfNeeded { - FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); - self.plugin.paymentQueueHandler = mockQueueHandler; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; FlutterError *error; + __block NSInteger showPriceConsentIfNeededCount = 0; + + queueStub.showPriceConsentIfNeededStub = ^(void) { + showPriceConsentIfNeededCount++; + }; + [self.plugin showPriceConsentIfNeededWithError:&error]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" if (@available(iOS 13.4, *)) { - OCMVerify(times(1), [mockQueueHandler showPriceConsentIfNeeded]); + XCTAssertEqual(showPriceConsentIfNeededCount, 1); } else { - OCMVerify(never(), [mockQueueHandler showPriceConsentIfNeeded]); + XCTAssertEqual(showPriceConsentIfNeededCount, 0); } #pragma clang diagnostic pop } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m index 2f8d5857c8d..0c6d51a3b75 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import "Stubs.h" @@ -10,10 +9,10 @@ @interface PaymentQueueTest : XCTestCase -@property(strong, nonatomic) NSDictionary *periodMap; -@property(strong, nonatomic) NSDictionary *discountMap; -@property(strong, nonatomic) NSDictionary *productMap; -@property(strong, nonatomic) NSDictionary *productResponseMap; +@property(nonatomic, strong) NSDictionary *periodMap; +@property(nonatomic, strong) NSDictionary *discountMap; +@property(nonatomic, strong) NSDictionary *productMap; +@property(nonatomic, strong) NSDictionary *productResponseMap; @end @@ -45,7 +44,7 @@ - (void)setUp { - (void)testTransactionPurchased { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get purchased transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchased; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -61,20 +60,20 @@ - (void)testTransactionPurchased { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased); - XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); + XCTAssertEqualObjects(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionFailed { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get failed transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateFailed; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -90,7 +89,7 @@ - (void)testTransactionFailed { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; @@ -104,7 +103,7 @@ - (void)testTransactionFailed { - (void)testTransactionRestored { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get restored transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateRestored; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -120,7 +119,7 @@ - (void)testTransactionRestored { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; @@ -128,13 +127,13 @@ - (void)testTransactionRestored { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored); - XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); + XCTAssertEqualObjects(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionPurchasing { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get purchasing transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchasing; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -150,7 +149,7 @@ - (void)testTransactionPurchasing { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; @@ -164,7 +163,7 @@ - (void)testTransactionPurchasing { - (void)testTransactionDeferred { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get deffered transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateDeferred; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -180,7 +179,7 @@ - (void)testTransactionDeferred { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; @@ -193,7 +192,7 @@ - (void)testTransactionDeferred { - (void)testFinishTransaction { XCTestExpectation *expectation = [self expectationWithDescription:@"handler.transactions should be empty."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateDeferred; __block FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { @@ -211,7 +210,7 @@ - (void)testFinishTransaction { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; @@ -220,9 +219,9 @@ - (void)testFinishTransaction { } - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmpty { - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] + [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } @@ -237,20 +236,41 @@ - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmp updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } - transactionCache:mockCache]; + transactionCache:cacheStub]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + return nil; + }; [handler startObservingPaymentQueue]; - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount); } - (void) testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheContainsEmptyTransactionArrays { - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] + [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } @@ -265,17 +285,36 @@ - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmp updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } - transactionCache:mockCache]; - - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[]); + transactionCache:cacheStub]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + return @[]; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + return @[]; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + return @[]; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; [handler startObservingPaymentQueue]; - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount); } - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { @@ -288,17 +327,17 @@ - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { XCTestExpectation *updateDownloadsExpectation = [self expectationWithDescription: @"downloadsUpdated callback should be called with one transaction."]; - SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class); - SKDownload *mockDownload = OCMClassMock(SKDownload.class); - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + SKPaymentTransaction *transactionStub = [[SKPaymentTransactionStub alloc] init]; + SKDownload *downloadStub = [[SKDownload alloc] init]; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] + [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [updateTransactionsExpectation fulfill]; } transactionRemoved:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [removeTransactionsExpectation fulfill]; } restoreTransactionFailed:nil @@ -307,20 +346,38 @@ - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { - XCTAssertEqualObjects(downloads, @[ mockDownload ]); + XCTAssertEqualObjects(downloads, @[ downloadStub ]); [updateDownloadsExpectation fulfill]; } - transactionCache:mockCache]; - - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[ - mockTransaction - ]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[ - mockDownload - ]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[ - mockTransaction - ]); + transactionCache:cacheStub]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + return @[ transactionStub ]; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + return @[ downloadStub ]; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + return @[ transactionStub ]; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; + + __block NSInteger clearInvokedCount = 0; + cacheStub.clearStub = ^{ + clearInvokedCount++; + }; [handler startObservingPaymentQueue]; @@ -328,15 +385,16 @@ - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation ] timeout:5]; - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); - OCMVerify(times(1), [mockCache clear]); + + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount); + XCTAssertEqual(1, clearInvokedCount); } - (void)testTransactionsShouldBeCachedWhenNotObserving { - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); @@ -352,18 +410,36 @@ - (void)testTransactionsShouldBeCachedWhenNotObserving { updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } - transactionCache:mockCache]; + transactionCache:cacheStub]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.addObjectsStub = ^(NSArray *_Nonnull objects, TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; + [handler addPayment:payment]; - OCMVerify(times(1), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyRemovedTransactions]); + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyRemovedTransactionsInvokedCount); } - (void)testTransactionsShouldNotBeCachedWhenObserving { @@ -376,18 +452,18 @@ - (void)testTransactionsShouldNotBeCachedWhenObserving { XCTestExpectation *updateDownloadsExpectation = [self expectationWithDescription: @"downloadsUpdated callback should be called with one transaction."]; - SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class); - SKDownload *mockDownload = OCMClassMock(SKDownload.class); - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + SKPaymentTransaction *transactionStub = [[SKPaymentTransactionStub alloc] init]; + SKDownload *downloadStub = [[SKDownload alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchased; - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [updateTransactionsExpectation fulfill]; } transactionRemoved:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [removeTransactionsExpectation fulfill]; } restoreTransactionFailed:nil @@ -396,25 +472,44 @@ - (void)testTransactionsShouldNotBeCachedWhenObserving { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { - XCTAssertEqualObjects(downloads, @[ mockDownload ]); + XCTAssertEqualObjects(downloads, @[ downloadStub ]); [updateDownloadsExpectation fulfill]; } - transactionCache:mockCache]; + transactionCache:cacheStub]; + + SKPaymentQueueStub *paymentQueueStub = [[SKPaymentQueueStub alloc] init]; [handler startObservingPaymentQueue]; - [handler paymentQueue:queue updatedTransactions:@[ mockTransaction ]]; - [handler paymentQueue:queue removedTransactions:@[ mockTransaction ]]; - [handler paymentQueue:queue updatedDownloads:@[ mockDownload ]]; + [handler paymentQueue:paymentQueueStub updatedTransactions:@[ transactionStub ]]; + [handler paymentQueue:paymentQueueStub removedTransactions:@[ transactionStub ]]; + [handler paymentQueue:paymentQueueStub updatedDownloads:@[ downloadStub ]]; [self waitForExpectations:@[ updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation ] timeout:5]; - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyRemovedTransactions]); + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.addObjectsStub = ^(NSArray *_Nonnull objects, TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; + XCTAssertEqual(0, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyRemovedTransactionsInvokedCount); } @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h index 2ef8e23181a..8e7769c5b5a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h @@ -4,6 +4,12 @@ #import #import +#import "FIATransactionCache.h" +#import "FLTMethodChannelProtocol.h" +#import "FLTPaymentQueueHandlerProtocol.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTRequestHandlerProtocol.h" +#import "FLTTransactionCacheProtocol.h" @import in_app_purchase_storekit; @@ -20,10 +26,11 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface SKProductStub : SKProduct - (instancetype)initWithMap:(NSDictionary *)map; +- (instancetype)initWithProductID:(NSString *)productIdentifier; @end @interface SKProductRequestStub : SKProductsRequest -@property(assign, nonatomic) BOOL returnError; +@property(nonatomic, assign) BOOL returnError; - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers; - (instancetype)initWithFailureError:(NSError *)error; @end @@ -32,15 +39,9 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) - (instancetype)initWithMap:(NSDictionary *)map; @end -@interface InAppPurchasePluginStub : InAppPurchasePlugin -@end - -@interface SKRequestStub : SKRequest -@end - @interface SKPaymentQueueStub : SKPaymentQueue -@property(assign, nonatomic) SKPaymentTransactionState testState; -@property(strong, nonatomic, nullable) id observer; +@property(nonatomic, assign) SKPaymentTransactionState testState; +@property(nonatomic, strong, nullable) id observer; @end @interface SKPaymentTransactionStub : SKPaymentTransaction @@ -60,7 +61,9 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface FIAPReceiptManagerStub : FIAPReceiptManager // Indicates whether getReceiptData of this stub is going to return an error. // Setting this to true will let getReceiptData give a basic NSError and return nil. -@property(assign, nonatomic) BOOL returnError; +@property(nonatomic, assign) BOOL returnError; +// Indicates whether the receipt url will be nil. +@property(nonatomic, assign) BOOL returnNilURL; @end @interface SKReceiptRefreshRequestStub : SKReceiptRefreshRequest @@ -72,4 +75,116 @@ API_AVAILABLE(ios(13.0), macos(10.15)) - (instancetype)initWithMap:(NSDictionary *)map; @end +// An interface representing a stubbed DefaultPaymentQueue +@interface PaymentQueueStub : NSObject + +// FLTPaymentQueueProtocol properties +@property(nonatomic, assign) SKPaymentTransactionState paymentState; +@property(nonatomic, strong, nullable) id observer; +@property(nonatomic, strong, readwrite) SKStorefront *storefront API_AVAILABLE(ios(13.0)); +@property(nonatomic, strong, readwrite) NSArray *transactions API_AVAILABLE( + ios(3.0), macos(10.7), watchos(6.2), visionos(1.0)); + +// Test Properties +@property(nonatomic, assign) + SKPaymentTransactionState testState; // Set this property to set a test Transaction state, then + // call addPayment to add it to the queue. +@property(nonatomic, strong, nonnull) + SKPaymentQueue *realQueue; // This is a reference to the real SKPaymentQueue + +// Stubs +@property(nonatomic, copy, nullable) void (^showPriceConsentIfNeededStub)(void); +@property(nonatomic, copy, nullable) void (^restoreTransactionsStub)(NSString *); +@property(nonatomic, copy, nullable) void (^startObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^stopObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^presentCodeRedemptionSheetStub)(void); +@property(nonatomic, copy, nullable) + NSArray * (^getUnfinishedTransactionsStub)(void); + +@end + +// An interface representing a stubbed DefaultTransactionCache +@interface TransactionCacheStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) NSArray * (^getObjectsForKeyStub)(TransactionCacheKey key); +@property(nonatomic, copy, nullable) void (^clearStub)(void); +@property(nonatomic, copy, nullable) void (^addObjectsStub)(NSArray *, TransactionCacheKey); + +@end + +// An interface representing a stubbed DefaultMethodChannel +@interface MethodChannelStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^invokeMethodChannelStub)(NSString *method, id arguments) + ; +@property(nonatomic, copy, nullable) void (^invokeMethodChannelWithResultsStub) + (NSString *method, id arguments, FlutterResult _Nullable); + +@end + +// An interface representing a stubbed DefaultPaymentQueueHandler +@interface PaymentQueueHandlerStub + : NSObject + +// Stubs +@property(nonatomic, copy, nullable) BOOL (^addPaymentStub)(SKPayment *payment); +@property(nonatomic, copy, nullable) void (^showPriceConsentIfNeededStub)(void); +@property(nonatomic, copy, nullable) void (^stopObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^startObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^presentCodeRedemptionSheetStub)(void); +@property(nonatomic, copy, nullable) void (^restoreTransactions)(NSString *); +@property(nonatomic, copy, nullable) + NSArray * (^getUnfinishedTransactionsStub)(void); +@property(nonatomic, copy, nullable) void (^finishTransactionStub)(SKPaymentTransaction *); +@property(nonatomic, copy, nullable) void (^paymentQueueUpdatedTransactionsStub) + (SKPaymentQueue *, NSArray *); + +@end + +// An interface representing a stubbed DefaultRequestHandler +@interface RequestHandlerStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^startProductRequestWithCompletionHandlerStub) + (ProductRequestCompletion); + +@end + +#if TARGET_OS_IOS +@interface FlutterPluginRegistrarStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^addApplicationDelegateStub)(NSObject *); +@property(nonatomic, copy, nullable) void (^addMethodCallDelegateStub) + (NSObject *, FlutterMethodChannel *); +@property(nonatomic, copy, nullable) NSString * (^lookupKeyForAssetStub)(NSString *); +@property(nonatomic, copy, nullable) NSString * (^lookupKeyForAssetFromPackageStub) + (NSString *, NSString *); +@property(nonatomic, copy, nullable) NSObject * (^messengerStub)(void); +@property(nonatomic, copy, nullable) void (^publishStub)(NSObject *); +@property(nonatomic, copy, nullable) void (^registerViewFactoryStub) + (NSObject *, NSString *); +@property(nonatomic, copy, nullable) NSObject * (^texturesStub)(void); +@property(nonatomic, copy, nullable) + void (^registerViewFactoryWithGestureRecognizersBlockingPolicyStub) + (NSObject *, NSString *, + FlutterPlatformViewGestureRecognizersBlockingPolicy); +@end +#endif + +@interface FlutterBinaryMessengerStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^cleanUpConnectionStub)(FlutterBinaryMessengerConnection) + ; +@property(nonatomic, copy, nullable) void (^sendOnChannelMessageStub)(NSString *, NSData *); +@property(nonatomic, copy, nullable) void (^sendOnChannelMessageBinaryReplyStub) + (NSString *, NSData *, FlutterBinaryReply); +@property(nonatomic, copy, nullable) + FlutterBinaryMessengerConnection (^setMessageHandlerOnChannelBinaryMessageHandlerStub) + (NSString *, FlutterBinaryMessageHandler); +@end + NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index b4dba710f02..6c7d246cfed 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -3,6 +3,14 @@ // found in the LICENSE file. #import "Stubs.h" +#import +#import + +#if TARGET_OS_OSX +#import +#else +#import +#endif @implementation SKProductSubscriptionPeriodStub @@ -87,8 +95,8 @@ - (instancetype)initWithProductID:(NSString *)productIdentifier { @interface SKProductRequestStub () -@property(strong, nonatomic) NSSet *identifers; -@property(strong, nonatomic) NSError *error; +@property(nonatomic, strong) NSSet *identifers; +@property(nonatomic, strong) NSError *error; @end @@ -144,28 +152,6 @@ - (instancetype)initWithMap:(NSDictionary *)map { @end -@interface InAppPurchasePluginStub () -@end - -@implementation InAppPurchasePluginStub - -- (SKProductRequestStub *)getProductRequestWithIdentifiers:(NSSet *)identifiers { - return [[SKProductRequestStub alloc] initWithProductIdentifiers:identifiers]; -} - -- (SKProduct *)getProduct:(NSString *)productID { - if ([productID isEqualToString:@""]) { - return nil; - } - return [[SKProductStub alloc] initWithProductID:productID]; -} - -- (SKReceiptRefreshRequestStub *)getRefreshReceiptRequest:(NSDictionary *)properties { - return [[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:properties]; -} - -@end - @interface SKPaymentQueueStub () @end @@ -293,6 +279,14 @@ - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error { return [[NSData alloc] initWithBase64EncodedString:originalString options:kNilOptions]; } +- (NSURL *)receiptURL { + if (self.returnNilURL) { + return nil; + } else { + return [[NSBundle mainBundle] appStoreReceiptURL]; + } +} + @end @implementation SKReceiptRefreshRequestStub { @@ -331,5 +325,325 @@ - (instancetype)initWithMap:(NSDictionary *)map { } return self; } +@end + +@implementation PaymentQueueStub + +@synthesize transactions; +@synthesize delegate; + +- (void)finishTransaction:(SKPaymentTransaction *)transaction { + [self.observer paymentQueue:self.realQueue removedTransactions:@[ transaction ]]; +} + +- (void)addPayment:(SKPayment *_Nonnull)payment { + SKPaymentTransactionStub *transaction = + [[SKPaymentTransactionStub alloc] initWithState:self.testState payment:payment]; + [self.observer paymentQueue:self.realQueue updatedTransactions:@[ transaction ]]; +} + +- (void)addTransactionObserver:(nonnull id)observer { + self.observer = observer; +} + +- (void)restoreCompletedTransactions { + [self.observer paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)self]; +} + +- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username { + [self.observer paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)self]; +} + +- (NSArray *_Nonnull)getUnfinishedTransactions { + if (self.getUnfinishedTransactionsStub) { + return self.getUnfinishedTransactionsStub(); + } else { + return @[]; + } +} + +#if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet { + if (self.presentCodeRedemptionSheetStub) { + self.presentCodeRedemptionSheetStub(); + } +} +#endif + +#if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded { + if (self.showPriceConsentIfNeededStub) { + self.showPriceConsentIfNeededStub(); + } +} +#endif + +- (void)restoreTransactions:(nullable NSString *)applicationName { + if (self.restoreTransactionsStub) { + self.restoreTransactionsStub(applicationName); + } +} + +- (void)startObservingPaymentQueue { + if (self.startObservingPaymentQueueStub) { + self.startObservingPaymentQueueStub(); + } +} + +- (void)stopObservingPaymentQueue { + if (self.stopObservingPaymentQueueStub) { + self.stopObservingPaymentQueueStub(); + } +} + +- (void)removeTransactionObserver:(id)observer { + self.observer = nil; +} +@end + +@implementation MethodChannelStub +- (void)invokeMethod:(nonnull NSString *)method arguments:(id _Nullable)arguments { + if (self.invokeMethodChannelStub) { + self.invokeMethodChannelStub(method, arguments); + } +} + +- (void)invokeMethod:(nonnull NSString *)method + arguments:(id _Nullable)arguments + result:(FlutterResult _Nullable)callback { + if (self.invokeMethodChannelWithResultsStub) { + self.invokeMethodChannelWithResultsStub(method, arguments, callback); + } +} + +@end + +@implementation TransactionCacheStub +- (void)addObjects:(nonnull NSArray *)objects forKey:(TransactionCacheKey)key { + if (self.addObjectsStub) { + self.addObjectsStub(objects, key); + } +} + +- (void)clear { + if (self.clearStub) { + self.clearStub(); + } +} + +- (nonnull NSArray *)getObjectsForKey:(TransactionCacheKey)key { + if (self.getObjectsForKeyStub) { + return self.getObjectsForKeyStub(key); + } + return @[]; +} +@end + +@implementation PaymentQueueHandlerStub + +@synthesize storefront; +@synthesize delegate; + +- (void)paymentQueue:(nonnull SKPaymentQueue *)queue + updatedTransactions:(nonnull NSArray *)transactions { + if (self.paymentQueueUpdatedTransactionsStub) { + self.paymentQueueUpdatedTransactionsStub(queue, transactions); + } +} + +#if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded { + if (self.showPriceConsentIfNeededStub) { + self.showPriceConsentIfNeededStub(); + } +} +#endif + +- (BOOL)addPayment:(nonnull SKPayment *)payment { + if (self.addPaymentStub) { + return self.addPaymentStub(payment); + } else { + return NO; + } +} + +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction { + if (self.finishTransactionStub) { + self.finishTransactionStub(transaction); + } +} + +- (nonnull NSArray *)getUnfinishedTransactions { + if (self.getUnfinishedTransactionsStub) { + return self.getUnfinishedTransactionsStub(); + } else { + return @[]; + } +} + +- (nonnull instancetype)initWithQueue:(nonnull id)queue + transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated + transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved + restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed + restoreCompletedTransactionsFinished: + (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished + shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment + updatedDownloads:(nullable UpdatedDownloads)updatedDownloads + transactionCache:(nonnull id)transactionCache { + return [[PaymentQueueHandlerStub alloc] init]; +} + +#if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet { + if (self.presentCodeRedemptionSheetStub) { + self.presentCodeRedemptionSheetStub(); + } +} +#endif + +- (void)restoreTransactions:(nullable NSString *)applicationName { + if (self.restoreTransactions) { + self.restoreTransactions(applicationName); + } +} + +- (void)startObservingPaymentQueue { + if (self.startObservingPaymentQueueStub) { + self.startObservingPaymentQueueStub(); + } +} + +- (void)stopObservingPaymentQueue { + if (self.stopObservingPaymentQueueStub) { + self.stopObservingPaymentQueueStub(); + } +} + +- (nonnull instancetype)initWithQueue:(nonnull id)queue + transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated + transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved + restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed + restoreCompletedTransactionsFinished: + (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished + shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment + updatedDownloads:(nullable UpdatedDownloads)updatedDownloads { + return [[PaymentQueueHandlerStub alloc] init]; +} + +@end + +@implementation RequestHandlerStub + +- (void)startProductRequestWithCompletionHandler:(nonnull ProductRequestCompletion)completion { + if (self.startProductRequestWithCompletionHandlerStub) { + self.startProductRequestWithCompletionHandlerStub(completion); + } +} +@end + +/// This mock is only used in iOS tests +#if TARGET_OS_IOS + +// This FlutterPluginRegistrar is a protocol, so to make a stub it has to be implemented. +@implementation FlutterPluginRegistrarStub + +- (void)addApplicationDelegate:(nonnull NSObject *)delegate { + if (self.addApplicationDelegateStub) { + self.addApplicationDelegateStub(delegate); + } +} + +- (void)addMethodCallDelegate:(nonnull NSObject *)delegate + channel:(nonnull FlutterMethodChannel *)channel { + if (self.addMethodCallDelegateStub) { + self.addMethodCallDelegateStub(delegate, channel); + } +} + +- (nonnull NSString *)lookupKeyForAsset:(nonnull NSString *)asset { + if (self.lookupKeyForAssetStub) { + return self.lookupKeyForAssetStub(asset); + } + return nil; +} + +- (nonnull NSString *)lookupKeyForAsset:(nonnull NSString *)asset + fromPackage:(nonnull NSString *)package { + if (self.lookupKeyForAssetFromPackageStub) { + return self.lookupKeyForAssetFromPackageStub(asset, package); + } + return nil; +} + +- (nonnull NSObject *)messenger { + if (self.messengerStub) { + return self.messengerStub(); + } + return [[FlutterBinaryMessengerStub alloc] init]; // Or default behavior +} + +- (void)publish:(nonnull NSObject *)value { + if (self.publishStub) { + self.publishStub(value); + } +} + +- (void)registerViewFactory:(nonnull NSObject *)factory + withId:(nonnull NSString *)factoryId { + if (self.registerViewFactoryStub) { + self.registerViewFactoryStub(factory, factoryId); + } +} +- (nonnull NSObject *)textures { + if (self.texturesStub) { + return self.texturesStub(); + } + return nil; +} + +- (void)registerViewFactory:(nonnull NSObject *)factory + withId:(nonnull NSString *)factoryId + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy { + if (self.registerViewFactoryWithGestureRecognizersBlockingPolicyStub) { + self.registerViewFactoryWithGestureRecognizersBlockingPolicyStub( + factory, factoryId, gestureRecognizersBlockingPolicy); + } +} + +@end + +// This FlutterBinaryMessenger is a protocol, so to make a stub it has to be implemented. +@implementation FlutterBinaryMessengerStub +- (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { + if (self.cleanUpConnectionStub) { + self.cleanUpConnectionStub(connection); + } +} + +- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message { + if (self.sendOnChannelMessageStub) { + self.sendOnChannelMessageStub(channel, message); + } +} + +- (void)sendOnChannel:(nonnull NSString *)channel + message:(NSData *_Nullable)message + binaryReply:(FlutterBinaryReply _Nullable)callback { + if (self.sendOnChannelMessageBinaryReplyStub) { + self.sendOnChannelMessageBinaryReplyStub(channel, message, callback); + } +} + +- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel + binaryMessageHandler: + (FlutterBinaryMessageHandler _Nullable)handler { + if (self.setMessageHandlerOnChannelBinaryMessageHandlerStub) { + return self.setMessageHandlerOnChannelBinaryMessageHandlerStub(channel, handler); + } + return 0; +} @end + +#endif diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart index acdc4a2979c..713a8c909e6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart @@ -158,9 +158,14 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// /// Uses the ISO 3166-1 Alpha-3 country code representation. /// See: https://developer.apple.com/documentation/storekit/skstorefront?language=objc - Future getCountryCode() async { - return (await _skPaymentQueueWrapper.storefront())?.countryCode; + @override + Future countryCode() async { + return (await _skPaymentQueueWrapper.storefront())?.countryCode ?? ''; } + + /// Use countryCode instead. + @Deprecated('Use countryCode') + Future getCountryCode() => countryCode(); } enum _TransactionRestoreState { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart index 7ce35bded2b..e136e05b5d1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart @@ -1,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 (v16.0.4), do not edit directly. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -720,7 +720,7 @@ class InAppPurchaseAPI { } } - Future finishTransaction(Map finishMap) async { + Future finishTransaction(Map finishMap) async { const String __pigeon_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction'; final BasicMessageChannel __pigeon_channel = diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart index fe1042c8037..6351c889cc3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart @@ -238,7 +238,7 @@ abstract class InAppPurchaseAPI { SKProductsResponseMessage startProductRequest( List productIdentifiers); - void finishTransaction(Map finishMap); + void finishTransaction(Map finishMap); void restoreTransactions(String? applicationUserName); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index d20cdec6ff1..bcb280781d0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.14 +version: 0.3.17 environment: sdk: ^3.2.3 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index f5a755d83b5..082d62939d9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -185,9 +185,10 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { } @override - void finishTransaction(Map finishMap) { + void finishTransaction(Map finishMap) { finishedTransactions.add(createPurchasedTransaction( - finishMap['productIdentifier']!, finishMap['transactionIdentifier']!, + finishMap['productIdentifier']! as String, + finishMap['transactionIdentifier']! as String, quantity: transactionList.first.payment.quantity)); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart index 97d13509fdb..56dc89fb190 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart @@ -576,8 +576,10 @@ void main() { const String expectedCountryCode = 'CA'; fakeStoreKitPlatform.setStoreFrontInfo( countryCode: expectedCountryCode, identifier: 'ABC'); - final String? countryCode = await iapStoreKitPlatform.getCountryCode(); + final String countryCode = await iapStoreKitPlatform.countryCode(); expect(countryCode, expectedCountryCode); + // Ensure deprecated code keeps working until removed. + expect(await iapStoreKitPlatform.countryCode(), expectedCountryCode); }); }); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index 82775f6b2e2..f7fe5283d94 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -239,7 +239,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { [dummyTransactionMessage]; @override - void finishTransaction(Map finishMap) { + void finishTransaction(Map finishMap) { transactionsFinished.add(Map.from(finishMap)); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart index 65cd6e0bcc2..6b0c9a77ab4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart @@ -1,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 (v16.0.4), do not edit directly. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports @@ -99,7 +99,7 @@ abstract class TestInAppPurchaseApi { Future startProductRequest( List productIdentifiers); - void finishTransaction(Map finishMap); + void finishTransaction(Map finishMap); void restoreTransactions(String? applicationUserName); @@ -278,10 +278,10 @@ abstract class TestInAppPurchaseApi { assert(message != null, 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null.'); final List args = (message as List?)!; - final Map? arg_finishMap = - (args[0] as Map?)?.cast(); + final Map? arg_finishMap = + (args[0] as Map?)?.cast(); assert(arg_finishMap != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null, expected non-null Map.'); + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null, expected non-null Map.'); try { api.finishTransaction(arg_finishMap!); return wrapResponse(empty: true); diff --git a/packages/integration_test/README.md b/packages/integration_test/README.md index 83c4adb500f..1f9442a6bee 100644 --- a/packages/integration_test/README.md +++ b/packages/integration_test/README.md @@ -15,4 +15,4 @@ dev_dependencies: ``` For the latest documentation, see [Integration -testing](https://flutter.dev/docs/testing/integration-tests). +testing](https://docs.flutter.dev/testing/integration-tests). diff --git a/packages/interactive_media_ads/CHANGELOG.md b/packages/interactive_media_ads/CHANGELOG.md index 477158a8871..a5ac6af7c71 100644 --- a/packages/interactive_media_ads/CHANGELOG.md +++ b/packages/interactive_media_ads/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.0.2+1 + +* Updates `README` with a usage section and fix app-facing interface documentation. + +## 0.0.2 + +* Adds Android implementation. + +## 0.0.1+3 + +* Fixes the pub badge source. + +## 0.0.1+2 + +* Bumps Android's androidx.annotation:annotation dependency from 1.5.0 to 1.8.0. + +## 0.0.1+1 + +* Adds Swift Package Manager support. + ## 0.0.1 * Adds platform interface for Android and iOS. diff --git a/packages/interactive_media_ads/README.md b/packages/interactive_media_ads/README.md index d1d17151f02..486f6aa69ec 100644 --- a/packages/interactive_media_ads/README.md +++ b/packages/interactive_media_ads/README.md @@ -2,9 +2,12 @@ Flutter plugin for the [Interactive Media Ads SDKs][1]. -[![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dev/packages/interactive_media_ads) +[![pub package](https://img.shields.io/pub/v/interactive_media_ads.svg)](https://pub.dev/packages/interactive_media_ads) -A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. +IMA SDKs make it easy to integrate multimedia ads into your websites and apps. IMA SDKs can request +ads from any [VAST-compliant][2] ad server and manage ad playback in your apps. With IMA client-side +SDKs, you maintain control of content video playback, while the SDK handles ad playback. Ads play in +a separate video player positioned on top of the app's content video player. | | Android | iOS | |-------------|---------|-------| @@ -12,4 +15,263 @@ A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. **This package is still in development.** +## IMA client-side overview + +Implementing IMA client-side involves five main SDK components, which are demonstrated in this +guide: + +* [AdDisplayContainer][3]: A container object where ads are rendered. +* [AdsLoader][4]: Requests ads and handles events from ads request responses. You should only +instantiate one ads loader, which can be reused throughout the life of the application. +* [AdsRequest][5]: An object that defines an ads request. Ads requests specify the URL for the VAST +ad tag, as well as additional parameters, such as ad dimensions. +* [AdsManager][6]: Contains the response to the ads request, controls ad playback, +and listens for ad events fired by the SDK. +* [AdsManagerDelegate][8]: Handles ad events and errors that occur during ad or stream +initialization and playback. + +## Usage + +This guide demonstrates how to integrate the IMA SDK into a new `Widget` using the [video_player][7] +plugin to display content. + +### 1. Add Android Required Permissions + +If building on Android, add the user permissions required by the IMA SDK for requesting ads in +`android/app/src/main/AndroidManifest.xml`. + + +```xml + + + + +``` + +### 2. Add Imports + +Add the import statements for the `interactive_media_ads` and [video_player][7]. Both plugins should +already be added to your `pubspec.yaml`. + + +```dart +import 'package:interactive_media_ads/interactive_media_ads.dart'; +import 'package:video_player/video_player.dart'; +``` + +### 3. Create a New Widget + +Create a new [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) +that handles displaying Ads and playing content. + + +```dart +/// Example widget displaying an Ad before a video. +class AdExampleWidget extends StatefulWidget { + /// Constructs an [AdExampleWidget]. + const AdExampleWidget({super.key}); + + @override + State createState() => _AdExampleWidgetState(); +} + +class _AdExampleWidgetState extends State { + // IMA sample tag for a single skippable inline video ad. See more IMA sample + // tags at https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags + static const String _adTagUrl = + 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator='; + + // The AdsLoader instance exposes the request ads method. + late final AdsLoader _adsLoader; + + // AdsManager exposes methods to control ad playback and listen to ad events. + AdsManager? _adsManager; + + // Whether the widget should be displaying the content video. The content + // player is hidden while Ads are playing. + bool _shouldShowContentVideo = true; + + // Controls the content video player. + late final VideoPlayerController _contentVideoController; + // ··· + @override + Widget build(BuildContext context) { + // ··· + } +} +``` + +### 4. Add the Video Players + +Instantiate the [AdDisplayContainer][3] for playing Ads and the +[VideoPlayerController](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController-class.html) +for playing content. + + +```dart +late final AdDisplayContainer _adDisplayContainer = AdDisplayContainer( + onContainerAdded: (AdDisplayContainer container) { + // Ads can't be requested until the `AdDisplayContainer` has been added to + // the native View hierarchy. + _requestAds(container); + }, +); + +@override +void initState() { + super.initState(); + _contentVideoController = VideoPlayerController.networkUrl( + Uri.parse( + 'https://storage.googleapis.com/gvabox/media/samples/stock.mp4', + ), + ) + ..addListener(() { + if (_contentVideoController.value.isCompleted) { + _adsLoader.contentComplete(); + setState(() {}); + } + }) + ..initialize().then((_) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + setState(() {}); + }); +} +``` + +### 5. Implement the `build` Method + +Return a `Widget` that contains the ad player and the content player. + + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SizedBox( + width: 300, + child: !_contentVideoController.value.isInitialized + ? Container() + : AspectRatio( + aspectRatio: _contentVideoController.value.aspectRatio, + child: Stack( + children: [ + // The display container must be on screen before any Ads can be + // loaded and can't be removed between ads. This handles clicks for + // ads. + _adDisplayContainer, + if (_shouldShowContentVideo) + VideoPlayer(_contentVideoController) + ], + ), + ), + ), + ), + floatingActionButton: + _contentVideoController.value.isInitialized && _shouldShowContentVideo + ? FloatingActionButton( + onPressed: () { + setState(() { + _contentVideoController.value.isPlaying + ? _contentVideoController.pause() + : _contentVideoController.play(); + }); + }, + child: Icon( + _contentVideoController.value.isPlaying + ? Icons.pause + : Icons.play_arrow, + ), + ) + : null, + ); +} +``` + +### 6. Request Ads + +Handle requesting ads and add event listeners to handle when content should be displayed or hidden. + + +```dart +Future _requestAds(AdDisplayContainer container) { + _adsLoader = AdsLoader( + container: container, + onAdsLoaded: (OnAdsLoadedData data) { + final AdsManager manager = data.manager; + _adsManager = data.manager; + + manager.setAdsManagerDelegate(AdsManagerDelegate( + onAdEvent: (AdEvent event) { + debugPrint('OnAdEvent: ${event.type}'); + switch (event.type) { + case AdEventType.loaded: + manager.start(); + case AdEventType.contentPauseRequested: + _pauseContent(); + case AdEventType.contentResumeRequested: + _resumeContent(); + case AdEventType.allAdsCompleted: + manager.destroy(); + _adsManager = null; + case AdEventType.clicked: + case AdEventType.complete: + } + }, + onAdErrorEvent: (AdErrorEvent event) { + debugPrint('AdErrorEvent: ${event.error.message}'); + _resumeContent(); + }, + )); + + manager.init(); + }, + onAdsLoadError: (AdsLoadErrorData data) { + debugPrint('OnAdsLoadError: ${data.error.message}'); + _resumeContent(); + }, + ); + + return _adsLoader.requestAds(AdsRequest(adTagUrl: _adTagUrl)); +} + +Future _resumeContent() { + setState(() { + _shouldShowContentVideo = true; + }); + return _contentVideoController.play(); +} + +Future _pauseContent() { + setState(() { + _shouldShowContentVideo = false; + }); + return _contentVideoController.pause(); +} +``` + +### 7. Dispose Resources + +Dispose the content player and the destroy the [AdsManager][6]. + + +```dart +@override +void dispose() { + super.dispose(); + _contentVideoController.dispose(); + _adsManager?.destroy(); +} +``` + +That's it! You're now requesting and displaying ads with the IMA SDK. To learn about additional SDK +features, see the [API reference](https://pub.dev/documentation/interactive_media_ads/latest/). + [1]: https://developers.google.com/interactive-media-ads +[2]: https://www.iab.com/guidelines/vast/ +[3]: https://pub.dev/documentation/interactive_media_ads/latest/interactive_media_ads/AdDisplayContainer-class.html +[4]: https://pub.dev/documentation/interactive_media_ads/latest/interactive_media_ads/AdsLoader-class.html +[5]: https://pub.dev/documentation/interactive_media_ads/latest/interactive_media_ads/AdsRequest-class.html +[6]: https://pub.dev/documentation/interactive_media_ads/latest/interactive_media_ads/AdsManager-class.html +[7]: https://pub.dev/packages/video_player +[8]: https://pub.dev/documentation/interactive_media_ads/latest/interactive_media_ads/AdsManagerDelegate-class.html diff --git a/packages/interactive_media_ads/android/build.gradle b/packages/interactive_media_ads/android/build.gradle index 3490e07ba38..282ddf2c7f4 100644 --- a/packages/interactive_media_ads/android/build.gradle +++ b/packages/interactive_media_ads/android/build.gradle @@ -50,11 +50,17 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.5.0' + implementation 'androidx.annotation:annotation:1.8.0' + implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.33.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation "org.mockito.kotlin:mockito-kotlin:4.1.0" testImplementation 'org.mockito:mockito-inline:5.1.0' testImplementation 'androidx.test:core:1.3.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.10")) } lintOptions { diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdDisplayContainerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdDisplayContainerProxyApi.kt new file mode 100644 index 00000000000..225abe61266 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdDisplayContainerProxyApi.kt @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +/** + * ProxyApi implementation for [com.google.ads.interactivemedia.v3.api.AdDisplayContainer]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdDisplayContainerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdDisplayContainer(pigeonRegistrar) diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorEventProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorEventProxyApi.kt new file mode 100644 index 00000000000..63e52492cd8 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorEventProxyApi.kt @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdError +import com.google.ads.interactivemedia.v3.api.AdErrorEvent + +/** + * ProxyApi implementation for [AdErrorEvent]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdErrorEventProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdErrorEvent(pigeonRegistrar) { + override fun error(pigeon_instance: AdErrorEvent): AdError { + return pigeon_instance.error + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorListenerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorListenerProxyApi.kt new file mode 100644 index 00000000000..0fc8ee25333 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorListenerProxyApi.kt @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdErrorEvent + +/** + * ProxyApi implementation for [AdErrorEvent.AdErrorListener]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdErrorListenerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdErrorListener(pigeonRegistrar) { + internal class AdErrorListenerImpl(val api: AdErrorListenerProxyApi) : + AdErrorEvent.AdErrorListener { + override fun onAdError(event: AdErrorEvent) { + api.pigeonRegistrar.runOnMainThread { api.onAdError(this, event) {} } + } + } + + override fun pigeon_defaultConstructor(): AdErrorEvent.AdErrorListener { + return AdErrorListenerImpl(this) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorProxyApi.kt new file mode 100644 index 00000000000..5e06fe2c3b7 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorProxyApi.kt @@ -0,0 +1,61 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdError + +/** + * ProxyApi implementation for [AdError]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdErrorProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdError(pigeonRegistrar) { + override fun errorCode(pigeon_instance: AdError): AdErrorCode { + return when (pigeon_instance.errorCode) { + AdError.AdErrorCode.ADS_PLAYER_NOT_PROVIDED -> AdErrorCode.ADS_PLAYER_WAS_NOT_PROVIDED + AdError.AdErrorCode.INTERNAL_ERROR -> AdErrorCode.INTERNAL_ERROR + AdError.AdErrorCode.VAST_MALFORMED_RESPONSE -> AdErrorCode.VAST_MALFORMED_RESPONSE + AdError.AdErrorCode.UNKNOWN_AD_RESPONSE -> AdErrorCode.UNKNOWN_AD_RESPONSE + AdError.AdErrorCode.VAST_TRAFFICKING_ERROR -> AdErrorCode.VAST_TRAFFICKING_ERROR + AdError.AdErrorCode.VAST_LOAD_TIMEOUT -> AdErrorCode.VAST_LOAD_TIMEOUT + AdError.AdErrorCode.VAST_TOO_MANY_REDIRECTS -> AdErrorCode.VAST_TOO_MANY_REDIRECTS + AdError.AdErrorCode.VAST_NO_ADS_AFTER_WRAPPER -> AdErrorCode.VAST_NO_ADS_AFTER_WRAPPER + AdError.AdErrorCode.VIDEO_PLAY_ERROR -> AdErrorCode.VIDEO_PLAY_ERROR + AdError.AdErrorCode.VAST_MEDIA_LOAD_TIMEOUT -> AdErrorCode.VAST_MEDIA_LOAD_TIMEOUT + AdError.AdErrorCode.VAST_LINEAR_ASSET_MISMATCH -> AdErrorCode.VAST_LINEAR_ASSET_MISMATCH + AdError.AdErrorCode.OVERLAY_AD_PLAYING_FAILED -> AdErrorCode.OVERLAY_AD_PLAYING_FAILED + AdError.AdErrorCode.OVERLAY_AD_LOADING_FAILED -> AdErrorCode.OVERLAY_AD_LOADING_FAILED + AdError.AdErrorCode.VAST_NONLINEAR_ASSET_MISMATCH -> AdErrorCode.VAST_NONLINEAR_ASSET_MISMATCH + AdError.AdErrorCode.COMPANION_AD_LOADING_FAILED -> AdErrorCode.COMPANION_AD_LOADING_FAILED + AdError.AdErrorCode.UNKNOWN_ERROR -> AdErrorCode.UNKNOWN_ERROR + AdError.AdErrorCode.VAST_EMPTY_RESPONSE -> AdErrorCode.VAST_EMPTY_RESPONSE + AdError.AdErrorCode.FAILED_TO_REQUEST_ADS -> AdErrorCode.FAILED_TO_REQUEST_ADS + AdError.AdErrorCode.VAST_ASSET_NOT_FOUND -> AdErrorCode.VAST_ASSET_NOT_FOUND + AdError.AdErrorCode.ADS_REQUEST_NETWORK_ERROR -> AdErrorCode.ADS_REQUEST_NETWORK_ERROR + AdError.AdErrorCode.INVALID_ARGUMENTS -> AdErrorCode.INVALID_ARGUMENTS + AdError.AdErrorCode.PLAYLIST_NO_CONTENT_TRACKING -> AdErrorCode.PLAYLIST_NO_CONTENT_TRACKING + AdError.AdErrorCode.UNEXPECTED_ADS_LOADED_EVENT -> AdErrorCode.UNEXPECTED_ADS_LOADED_EVENT + else -> AdErrorCode.UNKNOWN + } + } + + override fun errorCodeNumber(pigeon_instance: AdError): Long { + return pigeon_instance.errorCodeNumber.toLong() + } + + override fun errorType(pigeon_instance: AdError): AdErrorType { + return when (pigeon_instance.errorType) { + AdError.AdErrorType.LOAD -> AdErrorType.LOAD + AdError.AdErrorType.PLAY -> AdErrorType.PLAY + else -> AdErrorType.UNKNOWN + } + } + + override fun message(pigeon_instance: AdError): String { + return pigeon_instance.message + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdEventListenerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdEventListenerProxyApi.kt new file mode 100644 index 00000000000..9037799d1a8 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdEventListenerProxyApi.kt @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdEvent + +/** + * ProxyApi implementation for [AdEvent.AdEventListener]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdEventListenerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdEventListener(pigeonRegistrar) { + internal class AdEventListenerImpl(val api: AdEventListenerProxyApi) : AdEvent.AdEventListener { + override fun onAdEvent(event: AdEvent) { + api.pigeonRegistrar.runOnMainThread { api.onAdEvent(this, event) {} } + } + } + + override fun pigeon_defaultConstructor(): AdEvent.AdEventListener { + return AdEventListenerImpl(this) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdEventProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdEventProxyApi.kt new file mode 100644 index 00000000000..c490a1643c9 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdEventProxyApi.kt @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdEvent + +/** + * ProxyApi implementation for [AdEvent]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdEventProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdEvent(pigeonRegistrar) { + override fun type(pigeon_instance: AdEvent): AdEventType { + return when (pigeon_instance.type) { + AdEvent.AdEventType.ALL_ADS_COMPLETED -> AdEventType.ALL_ADS_COMPLETED + AdEvent.AdEventType.AD_BREAK_FETCH_ERROR -> AdEventType.AD_BREAK_FETCH_ERROR + AdEvent.AdEventType.CLICKED -> AdEventType.CLICKED + AdEvent.AdEventType.COMPLETED -> AdEventType.COMPLETED + AdEvent.AdEventType.CUEPOINTS_CHANGED -> AdEventType.CUEPOINTS_CHANGED + AdEvent.AdEventType.CONTENT_PAUSE_REQUESTED -> AdEventType.CONTENT_PAUSE_REQUESTED + AdEvent.AdEventType.CONTENT_RESUME_REQUESTED -> AdEventType.CONTENT_RESUME_REQUESTED + AdEvent.AdEventType.FIRST_QUARTILE -> AdEventType.FIRST_QUARTILE + AdEvent.AdEventType.LOG -> AdEventType.LOG + AdEvent.AdEventType.AD_BREAK_READY -> AdEventType.AD_BREAK_READY + AdEvent.AdEventType.MIDPOINT -> AdEventType.MIDPOINT + AdEvent.AdEventType.PAUSED -> AdEventType.PAUSED + AdEvent.AdEventType.RESUMED -> AdEventType.RESUMED + AdEvent.AdEventType.SKIPPABLE_STATE_CHANGED -> AdEventType.SKIPPABLE_STATE_CHANGED + AdEvent.AdEventType.SKIPPED -> AdEventType.SKIPPED + AdEvent.AdEventType.STARTED -> AdEventType.STARTED + AdEvent.AdEventType.TAPPED -> AdEventType.TAPPED + AdEvent.AdEventType.ICON_TAPPED -> AdEventType.ICON_TAPPED + AdEvent.AdEventType.ICON_FALLBACK_IMAGE_CLOSED -> AdEventType.ICON_FALLBACK_IMAGE_CLOSED + AdEvent.AdEventType.THIRD_QUARTILE -> AdEventType.THIRD_QUARTILE + AdEvent.AdEventType.LOADED -> AdEventType.LOADED + AdEvent.AdEventType.AD_PROGRESS -> AdEventType.AD_PROGRESS + AdEvent.AdEventType.AD_BUFFERING -> AdEventType.AD_BUFFERING + AdEvent.AdEventType.AD_BREAK_STARTED -> AdEventType.AD_BREAK_STARTED + AdEvent.AdEventType.AD_BREAK_ENDED -> AdEventType.AD_BREAK_ENDED + AdEvent.AdEventType.AD_PERIOD_STARTED -> AdEventType.AD_PERIOD_STARTED + AdEvent.AdEventType.AD_PERIOD_ENDED -> AdEventType.AD_PERIOD_ENDED + else -> AdEventType.UNKNOWN + } + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdMediaInfoProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdMediaInfoProxyApi.kt new file mode 100644 index 00000000000..cb1e03500b1 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdMediaInfoProxyApi.kt @@ -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. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + +/** + * ProxyApi implementation for [AdMediaInfo]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdMediaInfoProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdMediaInfo(pigeonRegistrar) { + override fun url(pigeon_instance: AdMediaInfo): String { + return pigeon_instance.url + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdPodInfoProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdPodInfoProxyApi.kt new file mode 100644 index 00000000000..367d5f5416a --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdPodInfoProxyApi.kt @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdPodInfo + +/** + * ProxyApi implementation for [AdPodInfo]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdPodInfoProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdPodInfo(pigeonRegistrar) { + override fun adPosition(pigeon_instance: AdPodInfo): Long { + return pigeon_instance.adPosition.toLong() + } + + override fun maxDuration(pigeon_instance: AdPodInfo): Double { + return pigeon_instance.maxDuration + } + + override fun podIndex(pigeon_instance: AdPodInfo): Long { + return pigeon_instance.podIndex.toLong() + } + + override fun timeOffset(pigeon_instance: AdPodInfo): Double { + return pigeon_instance.timeOffset + } + + override fun totalAds(pigeon_instance: AdPodInfo): Long { + return pigeon_instance.totalAds.toLong() + } + + override fun isBumper(pigeon_instance: AdPodInfo): Boolean { + return pigeon_instance.isBumper + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoadedListenerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoadedListenerProxyApi.kt new file mode 100644 index 00000000000..2054e84dac3 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoadedListenerProxyApi.kt @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsLoader +import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent + +/** + * ProxyApi implementation for [AdsLoader.AdsLoadedListener]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdsLoadedListenerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdsLoadedListener(pigeonRegistrar) { + internal class AdsLoadedListenerImpl(val api: AdsLoadedListenerProxyApi) : + AdsLoader.AdsLoadedListener { + override fun onAdsManagerLoaded(event: AdsManagerLoadedEvent) { + api.pigeonRegistrar.runOnMainThread { api.onAdsManagerLoaded(this, event) {} } + } + } + + override fun pigeon_defaultConstructor(): AdsLoader.AdsLoadedListener { + return AdsLoadedListenerImpl(this) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoaderProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoaderProxyApi.kt new file mode 100644 index 00000000000..5b5d1d7721e --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoaderProxyApi.kt @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdErrorEvent +import com.google.ads.interactivemedia.v3.api.AdsLoader +import com.google.ads.interactivemedia.v3.api.AdsRequest + +/** + * ProxyApi implementation for [AdsLoader]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdsLoaderProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdsLoader(pigeonRegistrar) { + override fun addAdErrorListener( + pigeon_instance: AdsLoader, + listener: AdErrorEvent.AdErrorListener + ) { + pigeon_instance.addAdErrorListener(listener) + } + + override fun addAdsLoadedListener( + pigeon_instance: AdsLoader, + listener: AdsLoader.AdsLoadedListener + ) { + pigeon_instance.addAdsLoadedListener(listener) + } + + override fun requestAds(pigeon_instance: AdsLoader, request: AdsRequest) { + pigeon_instance.requestAds(request) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerLoadedEventProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerLoadedEventProxyApi.kt new file mode 100644 index 00000000000..1374ecde4a1 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerLoadedEventProxyApi.kt @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsManager +import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent + +/** + * ProxyApi implementation for [AdsManagerLoadedEvent]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdsManagerLoadedEventProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdsManagerLoadedEvent(pigeonRegistrar) { + override fun manager(pigeon_instance: AdsManagerLoadedEvent): AdsManager { + return pigeon_instance.adsManager + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerProxyApi.kt new file mode 100644 index 00000000000..022a81417e2 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerProxyApi.kt @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsManager + +/** + * ProxyApi implementation for [AdsManager]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdsManagerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdsManager(pigeonRegistrar) { + override fun discardAdBreak(pigeon_instance: AdsManager) { + pigeon_instance.discardAdBreak() + } + + override fun pause(pigeon_instance: AdsManager) { + pigeon_instance.pause() + } + + override fun start(pigeon_instance: AdsManager) { + pigeon_instance.start() + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt new file mode 100644 index 00000000000..0f6746fe1c2 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsRequest +import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider + +/** + * ProxyApi implementation for [AdsRequest]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class AdsRequestProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiAdsRequest(pigeonRegistrar) { + companion object { + /** + * The current version of the `interactive_media_ads` plugin. + * + * This must match the version in pubspec.yaml. + */ + const val pluginVersion = "0.0.2+1" + } + + override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) { + pigeon_instance.adTagUrl = "$adTagUrl&request_agent=Flutter-IMA-$pluginVersion" + } + + override fun setContentProgressProvider( + pigeon_instance: AdsRequest, + provider: ContentProgressProvider + ) { + pigeon_instance.contentProgressProvider = provider + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/BaseDisplayContainerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/BaseDisplayContainerProxyApi.kt new file mode 100644 index 00000000000..6499138ad9c --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/BaseDisplayContainerProxyApi.kt @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +/** + * ProxyApi implementation for [com.google.ads.interactivemedia.v3.api.BaseDisplayContainer]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class BaseDisplayContainerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiBaseDisplayContainer(pigeonRegistrar) diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/BaseManagerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/BaseManagerProxyApi.kt new file mode 100644 index 00000000000..92d7558fa7c --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/BaseManagerProxyApi.kt @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdErrorEvent +import com.google.ads.interactivemedia.v3.api.AdEvent +import com.google.ads.interactivemedia.v3.api.BaseManager + +/** + * ProxyApi implementation for [BaseManager]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class BaseManagerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiBaseManager(pigeonRegistrar) { + override fun addAdErrorListener( + pigeon_instance: BaseManager, + errorListener: AdErrorEvent.AdErrorListener + ) { + pigeon_instance.addAdErrorListener(errorListener) + } + + override fun addAdEventListener( + pigeon_instance: BaseManager, + adEventListener: AdEvent.AdEventListener + ) { + pigeon_instance.addAdEventListener(adEventListener) + } + + override fun destroy(pigeon_instance: BaseManager) { + pigeon_instance.destroy() + } + + override fun init(pigeon_instance: BaseManager) { + pigeon_instance.init() + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ContentProgressProviderProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ContentProgressProviderProxyApi.kt new file mode 100644 index 00000000000..0fa1308d138 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ContentProgressProviderProxyApi.kt @@ -0,0 +1,15 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +/** + * ProxyApi implementation for + * [com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class ContentProgressProviderProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiContentProgressProvider(pigeonRegistrar) diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/FrameLayoutProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/FrameLayoutProxyApi.kt new file mode 100644 index 00000000000..c2d6ee34278 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/FrameLayoutProxyApi.kt @@ -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. + +package dev.flutter.packages.interactive_media_ads + +import android.widget.FrameLayout + +/** + * ProxyApi implementation for [FrameLayout]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class FrameLayoutProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiFrameLayout(pigeonRegistrar) { + override fun pigeon_defaultConstructor(): FrameLayout { + return FrameLayout(pigeonRegistrar.context) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkFactoryProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkFactoryProxyApi.kt new file mode 100644 index 00000000000..eaed1baabe2 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkFactoryProxyApi.kt @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.view.ViewGroup +import com.google.ads.interactivemedia.v3.api.AdDisplayContainer +import com.google.ads.interactivemedia.v3.api.AdsLoader +import com.google.ads.interactivemedia.v3.api.AdsRequest +import com.google.ads.interactivemedia.v3.api.ImaSdkFactory +import com.google.ads.interactivemedia.v3.api.ImaSdkSettings +import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer + +/** + * ProxyApi implementation for [ImaSdkFactory]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class ImaSdkFactoryProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiImaSdkFactory(pigeonRegistrar) { + override fun instance(): ImaSdkFactory { + return ImaSdkFactory.getInstance() + } + + override fun createAdDisplayContainer( + container: ViewGroup, + player: VideoAdPlayer + ): AdDisplayContainer { + return ImaSdkFactory.createAdDisplayContainer(container, player) + } + + override fun createImaSdkSettings(pigeon_instance: ImaSdkFactory): ImaSdkSettings { + return pigeon_instance.createImaSdkSettings() + } + + override fun createAdsLoader( + pigeon_instance: ImaSdkFactory, + settings: ImaSdkSettings, + container: AdDisplayContainer + ): AdsLoader { + return pigeon_instance.createAdsLoader(pigeonRegistrar.context, settings, container) + } + + override fun createAdsRequest(pigeon_instance: ImaSdkFactory): AdsRequest { + return pigeon_instance.createAdsRequest() + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkSettingsProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkSettingsProxyApi.kt new file mode 100644 index 00000000000..c95fd1d71e0 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkSettingsProxyApi.kt @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +/** + * ProxyApi implementation for [com.google.ads.interactivemedia.v3.api.ImaSdkSettings]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class ImaSdkSettingsProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiImaSdkSettings(pigeonRegistrar) diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt new file mode 100644 index 00000000000..49d21f7b51e --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt @@ -0,0 +1,3625 @@ +// 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 (v19.1.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass", "SyntheticAccessor") + +package dev.flutter.packages.interactive_media_ads + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private fun wrapResult(result: Any?): List { + return listOf(result) +} + +private fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf(exception.code, exception.message, exception.details) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + } +} + +private fun createConnectionError(channelName: String): FlutterError { + return FlutterError( + "channel-error", "Unable to establish connection on channel: '$channelName'.", "") +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +/** + * Maintains instances used to communicate with the corresponding objects in Dart. + * + *

Objects stored in this container are represented by an object in Dart that is also stored in + * an InstanceManager with the same identifier. + * + *

When an instance is added with an identifier, either can be used to retrieve the other. + * + *

Added instances are added as a weak reference and a strong reference. When the strong + * reference is removed with [remove] and the weak reference is deallocated, the + * `finalizationListener` is made with the instance's identifier. However, if the strong reference + * is removed and then the identifier is retrieved with the intention to pass the identifier to Dart + * (e.g. calling [getIdentifierForStrongReference]), the strong reference to the instance is + * recreated. The strong reference will then need to be removed manually again. + */ +@Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") +class PigeonInstanceManager(private val finalizationListener: PigeonFinalizationListener) { + /** Interface for listening when a weak reference of an instance is removed from the manager. */ + interface PigeonFinalizationListener { + fun onFinalize(identifier: Long) + } + + private val identifiers = java.util.WeakHashMap() + private val weakInstances = HashMap>() + private val strongInstances = HashMap() + private val referenceQueue = java.lang.ref.ReferenceQueue() + private val weakReferencesToIdentifiers = HashMap, Long>() + private val handler = android.os.Handler(android.os.Looper.getMainLooper()) + private var nextIdentifier: Long = minHostCreatedIdentifier + private var hasFinalizationListenerStopped = false + + /** + * Modifies the time interval used to define how often this instance removes garbage collected + * weak references to native Android objects that this instance was managing. + */ + var clearFinalizedWeakReferencesInterval: Long = 3000 + set(value) { + handler.removeCallbacks { this.releaseAllFinalizedInstances() } + field = value + releaseAllFinalizedInstances() + } + + init { + handler.postDelayed({ releaseAllFinalizedInstances() }, clearFinalizedWeakReferencesInterval) + } + + companion object { + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously from Dart. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + private const val minHostCreatedIdentifier: Long = 65536 + private const val tag = "PigeonInstanceManager" + + /** + * Instantiate a new manager. + * + * When the manager is no longer needed, [stopFinalizationListener] must be called. + * + * @param finalizationListener the listener for garbage collected weak references. + * @return a new `PigeonInstanceManager`. + */ + fun create(finalizationListener: PigeonFinalizationListener): PigeonInstanceManager { + return PigeonInstanceManager(finalizationListener) + } + } + + /** + * Removes `identifier` and its associated strongly referenced instance, if present, from the + * manager. + * + * @param identifier the identifier paired to an instance. + * @param the expected return type. + * @return the removed instance if the manager contains the given identifier, otherwise `null` if + * the manager doesn't contain the value. + */ + fun remove(identifier: Long): T? { + logWarningIfFinalizationListenerHasStopped() + return strongInstances.remove(identifier) as T? + } + + /** + * Retrieves the identifier paired with an instance. + * + * If the manager contains a strong reference to `instance`, it will return the identifier + * associated with `instance`. If the manager contains only a weak reference to `instance`, a new + * strong reference to `instance` will be added and will need to be removed again with [remove]. + * + * If this method returns a nonnull identifier, this method also expects the Dart + * `PigeonInstanceManager` to have, or recreate, a weak reference to the Dart instance the + * identifier is associated with. + * + * @param instance an instance that may be stored in the manager. + * @return the identifier associated with `instance` if the manager contains the value, otherwise + * `null` if the manager doesn't contain the value. + */ + fun getIdentifierForStrongReference(instance: Any?): Long? { + logWarningIfFinalizationListenerHasStopped() + val identifier = identifiers[instance] + if (identifier != null) { + strongInstances[identifier] = instance!! + } + return identifier + } + + /** + * Adds a new instance that was instantiated from Dart. + * + * The same instance can be added multiple times, but each identifier must be unique. This allows + * two objects that are equivalent (e.g. the `equals` method returns true and their hashcodes are + * equal) to both be added. + * + * @param instance the instance to be stored. + * @param identifier the identifier to be paired with instance. This value must be >= 0 and + * unique. + */ + fun addDartCreatedInstance(instance: Any, identifier: Long) { + logWarningIfFinalizationListenerHasStopped() + addInstance(instance, identifier) + } + + /** + * Adds a new instance that was instantiated from the host platform. + * + * @param instance the instance to be stored. This must be unique to all other added instances. + * @return the unique identifier (>= 0) stored with instance. + */ + fun addHostCreatedInstance(instance: Any): Long { + logWarningIfFinalizationListenerHasStopped() + require(!containsInstance(instance)) { + "Instance of ${instance.javaClass} has already been added." + } + val identifier = nextIdentifier++ + addInstance(instance, identifier) + return identifier + } + + /** + * Retrieves the instance associated with identifier. + * + * @param identifier the identifier associated with an instance. + * @param the expected return type. + * @return the instance associated with `identifier` if the manager contains the value, otherwise + * `null` if the manager doesn't contain the value. + */ + fun getInstance(identifier: Long): T? { + logWarningIfFinalizationListenerHasStopped() + val instance = weakInstances[identifier] as java.lang.ref.WeakReference? + return instance?.get() + } + + /** + * Returns whether this manager contains the given `instance`. + * + * @param instance the instance whose presence in this manager is to be tested. + * @return whether this manager contains the given `instance`. + */ + fun containsInstance(instance: Any?): Boolean { + logWarningIfFinalizationListenerHasStopped() + return identifiers.containsKey(instance) + } + + /** + * Stop the periodic run of the [PigeonFinalizationListener] for instances that have been garbage + * collected. + * + * The InstanceManager can continue to be used, but the [PigeonFinalizationListener] will no + * longer be called and methods will log a warning. + */ + fun stopFinalizationListener() { + handler.removeCallbacks { this.releaseAllFinalizedInstances() } + hasFinalizationListenerStopped = true + } + + /** + * Removes all of the instances from this manager. + * + * The manager will be empty after this call returns. + */ + fun clear() { + identifiers.clear() + weakInstances.clear() + strongInstances.clear() + weakReferencesToIdentifiers.clear() + } + + /** + * Whether the [PigeonFinalizationListener] is still being called for instances that are garbage + * collected. + * + * See [stopFinalizationListener]. + */ + fun hasFinalizationListenerStopped(): Boolean { + return hasFinalizationListenerStopped + } + + private fun releaseAllFinalizedInstances() { + if (hasFinalizationListenerStopped()) { + return + } + var reference: java.lang.ref.WeakReference? + while ((referenceQueue.poll() as java.lang.ref.WeakReference?).also { reference = it } != + null) { + val identifier = weakReferencesToIdentifiers.remove(reference) + if (identifier != null) { + weakInstances.remove(identifier) + strongInstances.remove(identifier) + finalizationListener.onFinalize(identifier) + } + } + handler.postDelayed({ releaseAllFinalizedInstances() }, clearFinalizedWeakReferencesInterval) + } + + private fun addInstance(instance: Any, identifier: Long) { + require(identifier >= 0) { "Identifier must be >= 0: $identifier" } + require(!weakInstances.containsKey(identifier)) { + "Identifier has already been added: $identifier" + } + val weakReference = java.lang.ref.WeakReference(instance, referenceQueue) + identifiers[instance] = identifier + weakInstances[identifier] = weakReference + weakReferencesToIdentifiers[weakReference] = identifier + strongInstances[identifier] = instance + } + + private fun logWarningIfFinalizationListenerHasStopped() { + if (hasFinalizationListenerStopped()) { + Log.w( + tag, + "The manager was used after calls to the PigeonFinalizationListener has been stopped.") + } + } +} + +/** Generated API for managing the Dart and native `PigeonInstanceManager`s. */ +private class PigeonInstanceManagerApi(val binaryMessenger: BinaryMessenger) { + companion object { + /** The codec used by PigeonInstanceManagerApi. */ + val codec: MessageCodec by lazy { StandardMessageCodec() } + + /** + * Sets up an instance of `PigeonInstanceManagerApi` to handle messages from the + * `binaryMessenger`. + */ + fun setUpMessageHandlers( + binaryMessenger: BinaryMessenger, + instanceManager: PigeonInstanceManager? + ) { + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.removeStrongReference", + codec) + if (instanceManager != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val identifierArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + instanceManager.remove(identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.clear", + codec) + if (instanceManager != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + instanceManager.clear() + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + fun removeStrongReference(identifierArg: Long, callback: (Result) -> Unit) { + val channelName = + "dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.removeStrongReference" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Provides implementations for each ProxyApi implementation and provides access to resources needed + * by any implementation. + */ +abstract class PigeonProxyApiRegistrar(val binaryMessenger: BinaryMessenger) { + val instanceManager: PigeonInstanceManager + private var _codec: StandardMessageCodec? = null + val codec: StandardMessageCodec + get() { + if (_codec == null) { + _codec = PigeonProxyApiBaseCodec(this) + } + return _codec!! + } + + init { + val api = PigeonInstanceManagerApi(binaryMessenger) + instanceManager = + PigeonInstanceManager.create( + object : PigeonInstanceManager.PigeonFinalizationListener { + override fun onFinalize(identifier: Long) { + api.removeStrongReference(identifier) { + if (it.isFailure) { + Log.e( + "PigeonProxyApiRegistrar", + "Failed to remove Dart strong reference with identifier: $identifier") + } + } + } + }) + } + + /** + * An implementation of [PigeonApiBaseDisplayContainer] used to add a new Dart instance of + * `BaseDisplayContainer` to the Dart `InstanceManager`. + */ + open fun getPigeonApiBaseDisplayContainer(): PigeonApiBaseDisplayContainer { + return PigeonApiBaseDisplayContainer(this) + } + + /** + * An implementation of [PigeonApiAdDisplayContainer] used to add a new Dart instance of + * `AdDisplayContainer` to the Dart `InstanceManager`. + */ + open fun getPigeonApiAdDisplayContainer(): PigeonApiAdDisplayContainer { + return PigeonApiAdDisplayContainer(this) + } + + /** + * An implementation of [PigeonApiAdsLoader] used to add a new Dart instance of `AdsLoader` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdsLoader(): PigeonApiAdsLoader + + /** + * An implementation of [PigeonApiAdsManagerLoadedEvent] used to add a new Dart instance of + * `AdsManagerLoadedEvent` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdsManagerLoadedEvent(): PigeonApiAdsManagerLoadedEvent + + /** + * An implementation of [PigeonApiAdErrorEvent] used to add a new Dart instance of `AdErrorEvent` + * to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdErrorEvent(): PigeonApiAdErrorEvent + + /** + * An implementation of [PigeonApiAdError] used to add a new Dart instance of `AdError` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdError(): PigeonApiAdError + + /** + * An implementation of [PigeonApiAdsRequest] used to add a new Dart instance of `AdsRequest` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdsRequest(): PigeonApiAdsRequest + + /** + * An implementation of [PigeonApiContentProgressProvider] used to add a new Dart instance of + * `ContentProgressProvider` to the Dart `InstanceManager`. + */ + open fun getPigeonApiContentProgressProvider(): PigeonApiContentProgressProvider { + return PigeonApiContentProgressProvider(this) + } + + /** + * An implementation of [PigeonApiAdsManager] used to add a new Dart instance of `AdsManager` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdsManager(): PigeonApiAdsManager + + /** + * An implementation of [PigeonApiBaseManager] used to add a new Dart instance of `BaseManager` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiBaseManager(): PigeonApiBaseManager + + /** + * An implementation of [PigeonApiAdEvent] used to add a new Dart instance of `AdEvent` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdEvent(): PigeonApiAdEvent + + /** + * An implementation of [PigeonApiImaSdkFactory] used to add a new Dart instance of + * `ImaSdkFactory` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiImaSdkFactory(): PigeonApiImaSdkFactory + + /** + * An implementation of [PigeonApiImaSdkSettings] used to add a new Dart instance of + * `ImaSdkSettings` to the Dart `InstanceManager`. + */ + open fun getPigeonApiImaSdkSettings(): PigeonApiImaSdkSettings { + return PigeonApiImaSdkSettings(this) + } + + /** + * An implementation of [PigeonApiVideoProgressUpdate] used to add a new Dart instance of + * `VideoProgressUpdate` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiVideoProgressUpdate(): PigeonApiVideoProgressUpdate + + /** + * An implementation of [PigeonApiAdMediaInfo] used to add a new Dart instance of `AdMediaInfo` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdMediaInfo(): PigeonApiAdMediaInfo + + /** + * An implementation of [PigeonApiAdPodInfo] used to add a new Dart instance of `AdPodInfo` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdPodInfo(): PigeonApiAdPodInfo + + /** + * An implementation of [PigeonApiFrameLayout] used to add a new Dart instance of `FrameLayout` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiFrameLayout(): PigeonApiFrameLayout + + /** + * An implementation of [PigeonApiViewGroup] used to add a new Dart instance of `ViewGroup` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiViewGroup(): PigeonApiViewGroup + + /** + * An implementation of [PigeonApiVideoView] used to add a new Dart instance of `VideoView` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiVideoView(): PigeonApiVideoView + + /** + * An implementation of [PigeonApiView] used to add a new Dart instance of `View` to the Dart + * `InstanceManager`. + */ + open fun getPigeonApiView(): PigeonApiView { + return PigeonApiView(this) + } + + /** + * An implementation of [PigeonApiMediaPlayer] used to add a new Dart instance of `MediaPlayer` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiMediaPlayer(): PigeonApiMediaPlayer + + /** + * An implementation of [PigeonApiVideoAdPlayerCallback] used to add a new Dart instance of + * `VideoAdPlayerCallback` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiVideoAdPlayerCallback(): PigeonApiVideoAdPlayerCallback + + /** + * An implementation of [PigeonApiVideoAdPlayer] used to add a new Dart instance of + * `VideoAdPlayer` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiVideoAdPlayer(): PigeonApiVideoAdPlayer + + /** + * An implementation of [PigeonApiAdsLoadedListener] used to add a new Dart instance of + * `AdsLoadedListener` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdsLoadedListener(): PigeonApiAdsLoadedListener + + /** + * An implementation of [PigeonApiAdErrorListener] used to add a new Dart instance of + * `AdErrorListener` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdErrorListener(): PigeonApiAdErrorListener + + /** + * An implementation of [PigeonApiAdEventListener] used to add a new Dart instance of + * `AdEventListener` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAdEventListener(): PigeonApiAdEventListener + + fun setUp() { + PigeonInstanceManagerApi.setUpMessageHandlers(binaryMessenger, instanceManager) + PigeonApiAdsLoader.setUpMessageHandlers(binaryMessenger, getPigeonApiAdsLoader()) + PigeonApiAdsRequest.setUpMessageHandlers(binaryMessenger, getPigeonApiAdsRequest()) + PigeonApiAdsManager.setUpMessageHandlers(binaryMessenger, getPigeonApiAdsManager()) + PigeonApiBaseManager.setUpMessageHandlers(binaryMessenger, getPigeonApiBaseManager()) + PigeonApiImaSdkFactory.setUpMessageHandlers(binaryMessenger, getPigeonApiImaSdkFactory()) + PigeonApiVideoProgressUpdate.setUpMessageHandlers( + binaryMessenger, getPigeonApiVideoProgressUpdate()) + PigeonApiFrameLayout.setUpMessageHandlers(binaryMessenger, getPigeonApiFrameLayout()) + PigeonApiViewGroup.setUpMessageHandlers(binaryMessenger, getPigeonApiViewGroup()) + PigeonApiVideoView.setUpMessageHandlers(binaryMessenger, getPigeonApiVideoView()) + PigeonApiMediaPlayer.setUpMessageHandlers(binaryMessenger, getPigeonApiMediaPlayer()) + PigeonApiVideoAdPlayerCallback.setUpMessageHandlers( + binaryMessenger, getPigeonApiVideoAdPlayerCallback()) + PigeonApiVideoAdPlayer.setUpMessageHandlers(binaryMessenger, getPigeonApiVideoAdPlayer()) + PigeonApiAdsLoadedListener.setUpMessageHandlers( + binaryMessenger, getPigeonApiAdsLoadedListener()) + PigeonApiAdErrorListener.setUpMessageHandlers(binaryMessenger, getPigeonApiAdErrorListener()) + PigeonApiAdEventListener.setUpMessageHandlers(binaryMessenger, getPigeonApiAdEventListener()) + } + + fun tearDown() { + PigeonInstanceManagerApi.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAdsLoader.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAdsRequest.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAdsManager.setUpMessageHandlers(binaryMessenger, null) + PigeonApiBaseManager.setUpMessageHandlers(binaryMessenger, null) + PigeonApiImaSdkFactory.setUpMessageHandlers(binaryMessenger, null) + PigeonApiVideoProgressUpdate.setUpMessageHandlers(binaryMessenger, null) + PigeonApiFrameLayout.setUpMessageHandlers(binaryMessenger, null) + PigeonApiViewGroup.setUpMessageHandlers(binaryMessenger, null) + PigeonApiVideoView.setUpMessageHandlers(binaryMessenger, null) + PigeonApiMediaPlayer.setUpMessageHandlers(binaryMessenger, null) + PigeonApiVideoAdPlayerCallback.setUpMessageHandlers(binaryMessenger, null) + PigeonApiVideoAdPlayer.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAdsLoadedListener.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAdErrorListener.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAdEventListener.setUpMessageHandlers(binaryMessenger, null) + } +} + +private class PigeonProxyApiBaseCodec(val registrar: PigeonProxyApiRegistrar) : + StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + return registrar.instanceManager.getInstance( + readValue(buffer).let { if (it is Int) it.toLong() else it as Long }) + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + if (value is com.google.ads.interactivemedia.v3.api.AdDisplayContainer) { + registrar.getPigeonApiAdDisplayContainer().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.BaseDisplayContainer) { + registrar.getPigeonApiBaseDisplayContainer().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdsLoader) { + registrar.getPigeonApiAdsLoader().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent) { + registrar.getPigeonApiAdsManagerLoadedEvent().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdErrorEvent) { + registrar.getPigeonApiAdErrorEvent().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdError) { + registrar.getPigeonApiAdError().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdsRequest) { + registrar.getPigeonApiAdsRequest().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider) { + registrar.getPigeonApiContentProgressProvider().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdsManager) { + registrar.getPigeonApiAdsManager().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.BaseManager) { + registrar.getPigeonApiBaseManager().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdEvent) { + registrar.getPigeonApiAdEvent().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.ImaSdkFactory) { + registrar.getPigeonApiImaSdkFactory().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.ImaSdkSettings) { + registrar.getPigeonApiImaSdkSettings().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate) { + registrar.getPigeonApiVideoProgressUpdate().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.player.AdMediaInfo) { + registrar.getPigeonApiAdMediaInfo().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdPodInfo) { + registrar.getPigeonApiAdPodInfo().pigeon_newInstance(value) {} + } else if (value is android.widget.FrameLayout) { + registrar.getPigeonApiFrameLayout().pigeon_newInstance(value) {} + } else if (value is android.view.ViewGroup) { + registrar.getPigeonApiViewGroup().pigeon_newInstance(value) {} + } else if (value is android.widget.VideoView) { + registrar.getPigeonApiVideoView().pigeon_newInstance(value) {} + } else if (value is android.view.View) { + registrar.getPigeonApiView().pigeon_newInstance(value) {} + } else if (value is android.media.MediaPlayer) { + registrar.getPigeonApiMediaPlayer().pigeon_newInstance(value) {} + } else if (value + is com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback) { + registrar.getPigeonApiVideoAdPlayerCallback().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer) { + registrar.getPigeonApiVideoAdPlayer().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener) { + registrar.getPigeonApiAdsLoadedListener().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener) { + registrar.getPigeonApiAdErrorListener().pigeon_newInstance(value) {} + } else if (value is com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener) { + registrar.getPigeonApiAdEventListener().pigeon_newInstance(value) {} + } + + when { + registrar.instanceManager.containsInstance(value) -> { + stream.write(128) + writeValue(stream, registrar.instanceManager.getIdentifierForStrongReference(value)) + } + else -> super.writeValue(stream, value) + } + } +} + +/** + * The types of error that can be encountered. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.AdErrorCode.html. + */ +enum class AdErrorCode(val raw: Int) { + /** Ads player was not provided. */ + ADS_PLAYER_WAS_NOT_PROVIDED(0), + /** There was a problem requesting ads from the server. */ + ADS_REQUEST_NETWORK_ERROR(1), + /** A companion ad failed to load or render. */ + COMPANION_AD_LOADING_FAILED(2), + /** There was a problem requesting ads from the server. */ + FAILED_TO_REQUEST_ADS(3), + /** An error internal to the SDK occurred. */ + INTERNAL_ERROR(4), + /** Invalid arguments were provided to SDK methods. */ + INVALID_ARGUMENTS(5), + /** An overlay ad failed to load. */ + OVERLAY_AD_LOADING_FAILED(6), + /** An overlay ad failed to render. */ + OVERLAY_AD_PLAYING_FAILED(7), + /** Ads list was returned but ContentProgressProvider was not configured. */ + PLAYLIST_NO_CONTENT_TRACKING(8), + /** Ads loader sent ads loaded event when it was not expected. */ + UNEXPECTED_ADS_LOADED_EVENT(9), + /** The ad response was not understood and cannot be parsed. */ + UNKNOWN_AD_RESPONSE(10), + /** An unexpected error occurred and the cause is not known. */ + UNKNOWN_ERROR(11), + /** No assets were found in the VAST ad response. */ + VAST_ASSET_NOT_FOUND(12), + /** A VAST response containing a single `` tag with no child tags. */ + VAST_EMPTY_RESPONSE(13), + /** + * Assets were found in the VAST ad response for a linear ad, but none of them matched the video + * player's capabilities. + */ + VAST_LINEAR_ASSET_MISMATCH(14), + /** + * At least one VAST wrapper ad loaded successfully and a subsequent wrapper or inline ad load has + * timed out. + */ + VAST_LOAD_TIMEOUT(15), + /** The ad response was not recognized as a valid VAST ad. */ + VAST_MALFORMED_RESPONSE(16), + /** Failed to load media assets from a VAST response. */ + VAST_MEDIA_LOAD_TIMEOUT(17), + /** + * Assets were found in the VAST ad response for a nonlinear ad, but none of them matched the + * video player's capabilities. + */ + VAST_NONLINEAR_ASSET_MISMATCH(18), + /** No Ads VAST response after one or more wrappers. */ + VAST_NO_ADS_AFTER_WRAPPER(19), + /** The maximum number of VAST wrapper redirects has been reached. */ + VAST_TOO_MANY_REDIRECTS(20), + /** + * Trafficking error. + * + * Video player received an ad type that it was not expecting and/or cannot display. + */ + VAST_TRAFFICKING_ERROR(21), + /** There was an error playing the video ad. */ + VIDEO_PLAY_ERROR(22), + /** The error code is not recognized by this wrapper. */ + UNKNOWN(23); + + companion object { + fun ofRaw(raw: Int): AdErrorCode? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * Specifies when the error was encountered, during either ad loading or playback. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.AdErrorType.html. + */ +enum class AdErrorType(val raw: Int) { + /** Indicates that the error was encountered when the ad was being loaded. */ + LOAD(0), + /** Indicates that the error was encountered after the ad loaded, during ad play. */ + PLAY(1), + /** The error is not recognized by this wrapper. */ + UNKNOWN(2); + + companion object { + fun ofRaw(raw: Int): AdErrorType? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * Types of events that can occur during ad playback. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.AdEventType.html. + */ +enum class AdEventType(val raw: Int) { + /** Fired when an ad break in a stream ends. */ + AD_BREAK_ENDED(0), + /** Fired when an ad break will not play back any ads. */ + AD_BREAK_FETCH_ERROR(1), + /** Fired when an ad break is ready from VMAP or ad rule ads. */ + AD_BREAK_READY(2), + /** Fired when an ad break in a stream starts. */ + AD_BREAK_STARTED(3), + /** Fired when playback stalls while the ad buffers. */ + AD_BUFFERING(4), + /** Fired when an ad period in a stream ends. */ + AD_PERIOD_ENDED(5), + /** Fired when an ad period in a stream starts. */ + AD_PERIOD_STARTED(6), + /** Fired to inform of ad progress and can be used by publisher to display a countdown timer. */ + AD_PROGRESS(7), + /** + * Fired when the ads manager is done playing all the valid ads in the ads response, or when the + * response doesn't return any valid ads. + */ + ALL_ADS_COMPLETED(8), + /** Fired when an ad is clicked. */ + CLICKED(9), + /** Fired when an ad completes playing. */ + COMPLETED(10), + /** Fired when content should be paused. */ + CONTENT_PAUSE_REQUESTED(11), + /** Fired when content should be resumed. */ + CONTENT_RESUME_REQUESTED(12), + /** Fired when VOD stream cuepoints have changed. */ + CUEPOINTS_CHANGED(13), + /** Fired when the ad playhead crosses first quartile. */ + FIRST_QUARTILE(14), + /** The user has closed the icon fallback image dialog. */ + ICON_FALLBACK_IMAGE_CLOSED(15), + /** The user has tapped an ad icon. */ + ICON_TAPPED(16), + /** Fired when the VAST response has been received. */ + LOADED(17), + /** Fired to enable the SDK to communicate a message to be logged, which is stored in adData. */ + LOG(18), + /** Fired when the ad playhead crosses midpoint. */ + MIDPOINT(19), + /** Fired when an ad is paused. */ + PAUSED(20), + /** Fired when an ad is resumed. */ + RESUMED(21), + /** Fired when an ad changes its skippable state. */ + SKIPPABLE_STATE_CHANGED(22), + /** Fired when an ad was skipped. */ + SKIPPED(23), + /** Fired when an ad starts playing. */ + STARTED(24), + /** Fired when a non-clickthrough portion of a video ad is clicked. */ + TAPPED(25), + /** Fired when the ad playhead crosses third quartile. */ + THIRD_QUARTILE(26), + /** The event type is not recognized by this wrapper. */ + UNKNOWN(27); + + companion object { + fun ofRaw(raw: Int): AdEventType? { + return values().firstOrNull { it.raw == raw } + } + } +} +/** + * A base class for more specialized container interfaces. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseDisplayContainer.html. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiBaseDisplayContainer(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of BaseDisplayContainer and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.BaseDisplayContainer, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.BaseDisplayContainer.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * A container in which to display the ads. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdDisplayContainer. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiAdDisplayContainer(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdDisplayContainer and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdDisplayContainer, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.AdDisplayContainer.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiBaseDisplayContainer] used to access callback methods */ + fun pigeon_getPigeonApiBaseDisplayContainer(): PigeonApiBaseDisplayContainer { + return pigeonRegistrar.getPigeonApiBaseDisplayContainer() + } +} +/** + * An object which allows publishers to request ads from ad servers or a dynamic ad insertion + * stream. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsLoader. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdsLoader(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** Registers a listener for errors that occur during the ads request. */ + abstract fun addAdErrorListener( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsLoader, + listener: com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener + ) + + /** Registers a listener for the ads manager loaded event. */ + abstract fun addAdsLoadedListener( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsLoader, + listener: com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener + ) + + /** Requests ads from a server. */ + abstract fun requestAds( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsLoader, + request: com.google.ads.interactivemedia.v3.api.AdsRequest + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAdsLoader?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsLoader.addAdErrorListener", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsLoader + val listenerArg = + args[1] as com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener + val wrapped: List = + try { + api.addAdErrorListener(pigeon_instanceArg, listenerArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsLoader.addAdsLoadedListener", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsLoader + val listenerArg = + args[1] as com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener + val wrapped: List = + try { + api.addAdsLoadedListener(pigeon_instanceArg, listenerArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsLoader.requestAds", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsLoader + val requestArg = args[1] as com.google.ads.interactivemedia.v3.api.AdsRequest + val wrapped: List = + try { + api.requestAds(pigeon_instanceArg, requestArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdsLoader and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdsLoader, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdsLoader.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * An event raised when ads are successfully loaded from the ad server through an AdsLoader. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsManagerLoadedEvent.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdsManagerLoadedEvent(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** + * The ads manager that will control playback of the loaded ads, or null when using dynamic ad + * insertion. + */ + abstract fun manager( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent + ): com.google.ads.interactivemedia.v3.api.AdsManager + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdsManagerLoadedEvent and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val managerArg = manager(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.AdsManagerLoadedEvent.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, managerArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * An event raised when there is an error loading or playing ads. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdErrorEvent.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdErrorEvent(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** The AdError that caused this event. */ + abstract fun error( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdErrorEvent + ): com.google.ads.interactivemedia.v3.api.AdError + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdErrorEvent and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdErrorEvent, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val errorArg = error(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdErrorEvent.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, errorArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * An error that occurred in the SDK. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdError(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** The error's code. */ + abstract fun errorCode( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdError + ): AdErrorCode + + /** The error code's number. */ + abstract fun errorCodeNumber( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdError + ): Long + + /** The error's type. */ + abstract fun errorType( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdError + ): AdErrorType + + /** A human-readable summary of the error. */ + abstract fun message(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdError): String + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdError and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdError, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val errorCodeArg = errorCode(pigeon_instanceArg) + val errorCodeNumberArg = errorCodeNumber(pigeon_instanceArg) + val errorTypeArg = errorType(pigeon_instanceArg) + val messageArg = message(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send( + listOf( + pigeon_identifierArg, + errorCodeArg.raw, + errorCodeNumberArg, + errorTypeArg.raw, + messageArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * An object containing the data used to request ads from the server. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsRequest. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdsRequest(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** Sets the URL from which ads will be requested. */ + abstract fun setAdTagUrl( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsRequest, + adTagUrl: String + ) + + /** + * Attaches a ContentProgressProvider instance to allow scheduling ad breaks based on content + * progress (cue points). + */ + abstract fun setContentProgressProvider( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsRequest, + provider: com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAdsRequest?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsRequest.setAdTagUrl", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsRequest + val adTagUrlArg = args[1] as String + val wrapped: List = + try { + api.setAdTagUrl(pigeon_instanceArg, adTagUrlArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsRequest.setContentProgressProvider", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsRequest + val providerArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider + val wrapped: List = + try { + api.setContentProgressProvider(pigeon_instanceArg, providerArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdsRequest and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdsRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdsRequest.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Defines an interface to allow SDK to track progress of the content video. + * + * See + * https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/android/api/reference/com/google/ads/interactivemedia/v3/api/player/ContentProgressProvider.html. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiContentProgressProvider(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ContentProgressProvider and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.ContentProgressProvider.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * An object which handles playing ads after they've been received from the server. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsManager. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdsManager(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** Discards current ad break and resumes content. */ + abstract fun discardAdBreak(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsManager) + + /** Pauses the current ad. */ + abstract fun pause(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsManager) + + /** Starts playing the ads. */ + abstract fun start(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdsManager) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAdsManager?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsManager.discardAdBreak", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsManager + val wrapped: List = + try { + api.discardAdBreak(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.interactive_media_ads.AdsManager.pause", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsManager + val wrapped: List = + try { + api.pause(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.interactive_media_ads.AdsManager.start", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.AdsManager + val wrapped: List = + try { + api.start(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdsManager and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdsManager, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdsManager.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiBaseManager] used to access callback methods */ + fun pigeon_getPigeonApiBaseManager(): PigeonApiBaseManager { + return pigeonRegistrar.getPigeonApiBaseManager() + } +} +/** + * Base interface for managing ads.. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseManager.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiBaseManager(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** + * Registers a listener for errors that occur during the ad or stream initialization and playback. + */ + abstract fun addAdErrorListener( + pigeon_instance: com.google.ads.interactivemedia.v3.api.BaseManager, + errorListener: com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener + ) + + /** + * Registers a listener for ad events that occur during ad or stream initialization and playback. + */ + abstract fun addAdEventListener( + pigeon_instance: com.google.ads.interactivemedia.v3.api.BaseManager, + adEventListener: com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener + ) + + /** Stops the ad and all tracking, then releases all assets that were loaded to play the ad. */ + abstract fun destroy(pigeon_instance: com.google.ads.interactivemedia.v3.api.BaseManager) + + /** Initializes the ad experience using default rendering settings */ + abstract fun init(pigeon_instance: com.google.ads.interactivemedia.v3.api.BaseManager) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiBaseManager?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.BaseManager.addAdErrorListener", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.BaseManager + val errorListenerArg = + args[1] as com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener + val wrapped: List = + try { + api.addAdErrorListener(pigeon_instanceArg, errorListenerArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.BaseManager.addAdEventListener", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.BaseManager + val adEventListenerArg = + args[1] as com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener + val wrapped: List = + try { + api.addAdEventListener(pigeon_instanceArg, adEventListenerArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.BaseManager.destroy", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.BaseManager + val wrapped: List = + try { + api.destroy(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.interactive_media_ads.BaseManager.init", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.BaseManager + val wrapped: List = + try { + api.init(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of BaseManager and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.BaseManager, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.BaseManager.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Event to notify publisher that an event occurred with an Ad. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdEvent(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** The type of event that occurred. */ + abstract fun type(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdEvent): AdEventType + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdEvent and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdEvent, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val typeArg = type(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdEvent.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, typeArg.raw)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Factory class for creating SDK objects. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/ImaSdkFactory. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiImaSdkFactory(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun instance(): com.google.ads.interactivemedia.v3.api.ImaSdkFactory + + abstract fun createAdDisplayContainer( + container: android.view.ViewGroup, + player: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer + ): com.google.ads.interactivemedia.v3.api.AdDisplayContainer + + /** Creates an `ImaSdkSettings` object for configuring the IMA SDK. */ + abstract fun createImaSdkSettings( + pigeon_instance: com.google.ads.interactivemedia.v3.api.ImaSdkFactory + ): com.google.ads.interactivemedia.v3.api.ImaSdkSettings + + /** Creates an `AdsLoader` for requesting ads using the specified settings object. */ + abstract fun createAdsLoader( + pigeon_instance: com.google.ads.interactivemedia.v3.api.ImaSdkFactory, + settings: com.google.ads.interactivemedia.v3.api.ImaSdkSettings, + container: com.google.ads.interactivemedia.v3.api.AdDisplayContainer + ): com.google.ads.interactivemedia.v3.api.AdsLoader + + /** Creates an AdsRequest object to contain the data used to request ads. */ + abstract fun createAdsRequest( + pigeon_instance: com.google.ads.interactivemedia.v3.api.ImaSdkFactory + ): com.google.ads.interactivemedia.v3.api.AdsRequest + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiImaSdkFactory?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.instance", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.instance(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createAdDisplayContainer", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val containerArg = args[0] as android.view.ViewGroup + val playerArg = args[1] as com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer + val wrapped: List = + try { + listOf(api.createAdDisplayContainer(containerArg, playerArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createImaSdkSettings", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.ImaSdkFactory + val wrapped: List = + try { + listOf(api.createImaSdkSettings(pigeon_instanceArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createAdsLoader", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.ImaSdkFactory + val settingsArg = args[1] as com.google.ads.interactivemedia.v3.api.ImaSdkSettings + val containerArg = args[2] as com.google.ads.interactivemedia.v3.api.AdDisplayContainer + val wrapped: List = + try { + listOf(api.createAdsLoader(pigeon_instanceArg, settingsArg, containerArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createAdsRequest", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.ImaSdkFactory + val wrapped: List = + try { + listOf(api.createAdsRequest(pigeon_instanceArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ImaSdkFactory and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.ImaSdkFactory, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Defines general SDK settings that are used when creating an `AdsLoader`. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.html. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiImaSdkSettings(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ImaSdkSettings and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.ImaSdkSettings, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.ImaSdkSettings.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Defines an update to the video's progress. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoProgressUpdate.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiVideoProgressUpdate(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor( + currentTimeMs: Long, + durationMs: Long + ): com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + + /** Value to use for cases when progress is not yet defined, such as video initialization. */ + abstract fun videoTimeNotReady(): + com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiVideoProgressUpdate?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val currentTimeMsArg = + args[1].let { num -> if (num is Int) num.toLong() else num as Long } + val durationMsArg = args[2].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(currentTimeMsArg, durationMsArg), + pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.videoTimeNotReady", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.videoTimeNotReady(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of VideoProgressUpdate and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * The minimal information required to play an ad. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/AdMediaInfo.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdMediaInfo(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun url( + pigeon_instance: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ): String + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdMediaInfo and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val urlArg = url(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdMediaInfo.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * An ad may be part of a pod of ads. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdPodInfo.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdPodInfo(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** + * The position of the ad within the pod. + * + * The value returned is one-based, for example, 1 of 2, 2 of 2, etc. If the ad is not part of a + * pod, this will return 1. + */ + abstract fun adPosition(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdPodInfo): Long + + /** + * The maximum duration of the pod in seconds. + * + * For unknown duration, -1 is returned. + */ + abstract fun maxDuration( + pigeon_instance: com.google.ads.interactivemedia.v3.api.AdPodInfo + ): Double + + /** Client side and DAI VOD: Returns the index of the ad pod. */ + abstract fun podIndex(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdPodInfo): Long + + /** + * The content time offset at which the current ad pod was scheduled. + * + * For preroll pod, 0 is returned. For midrolls, the scheduled time is returned in seconds. For + * postroll, -1 is returned. Defaults to 0 if this ad is not part of a pod, or the pod is not part + * of an ad playlist. + */ + abstract fun timeOffset(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdPodInfo): Double + + /** The total number of ads contained within this pod, including bumpers. */ + abstract fun totalAds(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdPodInfo): Long + + /** Returns true if the ad is a bumper ad. */ + abstract fun isBumper(pigeon_instance: com.google.ads.interactivemedia.v3.api.AdPodInfo): Boolean + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdPodInfo and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdPodInfo, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val adPositionArg = adPosition(pigeon_instanceArg) + val maxDurationArg = maxDuration(pigeon_instanceArg) + val podIndexArg = podIndex(pigeon_instanceArg) + val timeOffsetArg = timeOffset(pigeon_instanceArg) + val totalAdsArg = totalAds(pigeon_instanceArg) + val isBumperArg = isBumper(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send( + listOf( + pigeon_identifierArg, + adPositionArg, + maxDurationArg, + podIndexArg, + timeOffsetArg, + totalAdsArg, + isBumperArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * FrameLayout is designed to block out an area on the screen to display a single item. + * + * See https://developer.android.com/reference/android/widget/FrameLayout. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiFrameLayout(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor(): android.widget.FrameLayout + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiFrameLayout?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.FrameLayout.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of FrameLayout and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.widget.FrameLayout, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.FrameLayout.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiViewGroup] used to access callback methods */ + fun pigeon_getPigeonApiViewGroup(): PigeonApiViewGroup { + return pigeonRegistrar.getPigeonApiViewGroup() + } +} +/** + * A special view that can contain other views (called children.) + * + * See https://developer.android.com/reference/android/view/ViewGroup. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiViewGroup(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun addView(pigeon_instance: android.view.ViewGroup, view: android.view.View) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiViewGroup?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.ViewGroup.addView", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.ViewGroup + val viewArg = args[1] as android.view.View + val wrapped: List = + try { + api.addView(pigeon_instanceArg, viewArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ViewGroup and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.view.ViewGroup, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.ViewGroup.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiView] used to access callback methods */ + fun pigeon_getPigeonApiView(): PigeonApiView { + return pigeonRegistrar.getPigeonApiView() + } +} +/** + * Displays a video file. + * + * See https://developer.android.com/reference/android/widget/VideoView. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiVideoView(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor(): android.widget.VideoView + + /** Sets the URI of the video. */ + abstract fun setVideoUri(pigeon_instance: android.widget.VideoView, uri: String) + + /** + * The current position of the playing video. + * + * In milliseconds. + */ + abstract fun getCurrentPosition(pigeon_instance: android.widget.VideoView): Long + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiVideoView?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoView.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoView.setVideoUri", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.widget.VideoView + val uriArg = args[1] as String + val wrapped: List = + try { + api.setVideoUri(pigeon_instanceArg, uriArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoView.getCurrentPosition", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.widget.VideoView + val wrapped: List = + try { + listOf(api.getCurrentPosition(pigeon_instanceArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of VideoView and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.widget.VideoView, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + throw IllegalStateException( + "Attempting to create a new Dart instance of VideoView, but the class has a nonnull callback method.") + } + + /** Callback to be invoked when the media source is ready for playback. */ + fun onPrepared( + pigeon_instanceArg: android.widget.VideoView, + playerArg: android.media.MediaPlayer, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoView.onPrepared" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, playerArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Callback to be invoked when playback of a media source has completed. */ + fun onCompletion( + pigeon_instanceArg: android.widget.VideoView, + playerArg: android.media.MediaPlayer, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoView.onCompletion" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, playerArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Callback to be invoked when there has been an error during an asynchronous operation. */ + fun onError( + pigeon_instanceArg: android.widget.VideoView, + playerArg: android.media.MediaPlayer, + whatArg: Long, + extraArg: Long, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoView.onError" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, playerArg, whatArg, extraArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiView] used to access callback methods */ + fun pigeon_getPigeonApiView(): PigeonApiView { + return pigeonRegistrar.getPigeonApiView() + } +} +/** + * This class represents the basic building block for user interface components. + * + * See https://developer.android.com/reference/android/view/View. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiView(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of View and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance(pigeon_instanceArg: android.view.View, callback: (Result) -> Unit) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.View.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * MediaPlayer class can be used to control playback of audio/video files and streams. + * + * See https://developer.android.com/reference/android/media/MediaPlayer. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiMediaPlayer(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** Gets the duration of the file. */ + abstract fun getDuration(pigeon_instance: android.media.MediaPlayer): Long + + /** Seeks to specified time position. */ + abstract fun seekTo(pigeon_instance: android.media.MediaPlayer, mSec: Long) + + /** Starts or resumes playback. */ + abstract fun start(pigeon_instance: android.media.MediaPlayer) + + /** Pauses playback. */ + abstract fun pause(pigeon_instance: android.media.MediaPlayer) + + /** Stops playback after playback has been started or paused. */ + abstract fun stop(pigeon_instance: android.media.MediaPlayer) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiMediaPlayer?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.MediaPlayer.getDuration", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.media.MediaPlayer + val wrapped: List = + try { + listOf(api.getDuration(pigeon_instanceArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.MediaPlayer.seekTo", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.media.MediaPlayer + val mSecArg = args[1].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.seekTo(pigeon_instanceArg, mSecArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.MediaPlayer.start", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.media.MediaPlayer + val wrapped: List = + try { + api.start(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.MediaPlayer.pause", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.media.MediaPlayer + val wrapped: List = + try { + api.pause(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.interactive_media_ads.MediaPlayer.stop", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.media.MediaPlayer + val wrapped: List = + try { + api.stop(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of MediaPlayer and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.media.MediaPlayer, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.MediaPlayer.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Callbacks that the player must fire. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoAdPlayer.VideoAdPlayerCallback.html + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiVideoAdPlayerCallback(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + /** Fire this callback periodically as ad playback occurs. */ + abstract fun onAdProgress( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + videoProgressUpdate: com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + ) + + /** Fire this callback when video playback stalls waiting for data. */ + abstract fun onBuffering( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when all content has finished playing. */ + abstract fun onContentComplete( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + ) + + /** Fire this callback when the video finishes playing. */ + abstract fun onEnded( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when the video has encountered an error. */ + abstract fun onError( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when the video is ready to begin playback. */ + abstract fun onLoaded( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when the video is paused. */ + abstract fun onPause( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when the player begins playing a video. */ + abstract fun onPlay( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when the video is unpaused. */ + abstract fun onResume( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + ) + + /** Fire this callback when the playback volume changes. */ + abstract fun onVolumeChanged( + pigeon_instance: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + percentage: Long + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers( + binaryMessenger: BinaryMessenger, + api: PigeonApiVideoAdPlayerCallback? + ) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onAdProgress", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val videoProgressUpdateArg = + args[2] as com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + val wrapped: List = + try { + api.onAdProgress(pigeon_instanceArg, adMediaInfoArg, videoProgressUpdateArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onBuffering", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onBuffering(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onContentComplete", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val wrapped: List = + try { + api.onContentComplete(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onEnded", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onEnded(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onError", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onError(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onLoaded", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onLoaded(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onPause", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onPause(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onPlay", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onPlay(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onResume", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val wrapped: List = + try { + api.onResume(pigeon_instanceArg, adMediaInfoArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onVolumeChanged", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback + val adMediaInfoArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.AdMediaInfo + val percentageArg = args[2].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.onVolumeChanged(pigeon_instanceArg, adMediaInfoArg, percentageArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of VideoAdPlayerCallback and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Defines the set of methods that a video player must implement to be used by the IMA SDK, as well + * as a set of callbacks that it must fire. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoAdPlayer.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiVideoAdPlayer(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor(): + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer + + /** The volume of the player as a percentage from 0 to 100. */ + abstract fun setVolume( + pigeon_instance: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + value: Long + ) + + /** The `VideoProgressUpdate` describing playback progress of the current video. */ + abstract fun setAdProgress( + pigeon_instance: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + progress: com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiVideoAdPlayer?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.setVolume", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] as com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer + val valueArg = args[1].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.setVolume(pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.setAdProgress", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] as com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer + val progressArg = + args[1] as com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + val wrapped: List = + try { + api.setAdProgress(pigeon_instanceArg, progressArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of VideoAdPlayer and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + throw IllegalStateException( + "Attempting to create a new Dart instance of VideoAdPlayer, but the class has a nonnull callback method.") + } + + /** Adds a callback. */ + fun addCallback( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + callbackArg: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.addCallback" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, callbackArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Loads a video ad hosted at AdMediaInfo. */ + fun loadAd( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + adMediaInfoArg: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + adPodInfoArg: com.google.ads.interactivemedia.v3.api.AdPodInfo, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.loadAd" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, adMediaInfoArg, adPodInfoArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Pauses playing the current ad. */ + fun pauseAd( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + adMediaInfoArg: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pauseAd" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, adMediaInfoArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** + * Starts or resumes playing the video ad referenced by the AdMediaInfo, provided loadAd has + * already been called for it. + */ + fun playAd( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + adMediaInfoArg: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.playAd" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, adMediaInfoArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Cleans up and releases all resources used by the `VideoAdPlayer`. */ + fun release( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.release" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Removes a callback. */ + fun removeCallback( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + callbackArg: + com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.removeCallback" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, callbackArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } + + /** Stops playing the current ad. */ + fun stopAd( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer, + adMediaInfoArg: com.google.ads.interactivemedia.v3.api.player.AdMediaInfo, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.stopAd" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, adMediaInfoArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Listener interface for notification of ad load or stream load completion. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsLoader.AdsLoadedListener.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdsLoadedListener(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor(): + com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAdsLoadedListener?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdsLoadedListener and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + throw IllegalStateException( + "Attempting to create a new Dart instance of AdsLoadedListener, but the class has a nonnull callback method.") + } + + /** Called once the AdsManager or StreamManager has been loaded. */ + fun onAdsManagerLoaded( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener, + eventArg: com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.onAdsManagerLoaded" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, eventArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Interface for classes that will listen to AdErrorEvents. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdErrorEvent.AdErrorListener.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdErrorListener(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor(): + com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAdErrorListener?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdErrorListener.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdErrorListener and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + throw IllegalStateException( + "Attempting to create a new Dart instance of AdErrorListener, but the class has a nonnull callback method.") + } + + /** Called when an error occurs. */ + fun onAdError( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener, + eventArg: com.google.ads.interactivemedia.v3.api.AdErrorEvent, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdErrorListener.onAdError" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, eventArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} +/** + * Listener interface for ad events. + * + * See + * https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.AdEventListener.html. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAdEventListener(open val pigeonRegistrar: PigeonProxyApiRegistrar) { + abstract fun pigeon_defaultConstructor(): + com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAdEventListener?) { + val codec = api?.pigeonRegistrar?.codec ?: StandardMessageCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.interactive_media_ads.AdEventListener.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AdEventListener and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + Result.success(Unit) + return + } + throw IllegalStateException( + "Attempting to create a new Dart instance of AdEventListener, but the class has a nonnull callback method.") + } + + /** Respond to an occurrence of an AdEvent. */ + fun onAdEvent( + pigeon_instanceArg: com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener, + eventArg: com.google.ads.interactivemedia.v3.api.AdEvent, + callback: (Result) -> Unit + ) { + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.interactive_media_ads.AdEventListener.onAdEvent" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, eventArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt index 50595236588..5dd456e0d72 100644 --- a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPlugin.kt @@ -4,34 +4,73 @@ package dev.flutter.packages.interactive_media_ads +import android.content.Context +import android.view.View import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory /** InteractiveMediaAdsPlugin */ -class InteractiveMediaAdsPlugin : FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel: MethodChannel +class InteractiveMediaAdsPlugin : FlutterPlugin, ActivityAware { + private lateinit var pluginBinding: FlutterPlugin.FlutterPluginBinding + private lateinit var registrar: ProxyApiRegistrar override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "interactive_media_ads") - channel.setMethodCallHandler(this) - } + pluginBinding = flutterPluginBinding - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } + registrar = + ProxyApiRegistrar(pluginBinding.binaryMessenger, context = pluginBinding.applicationContext) + registrar.setUp() + + flutterPluginBinding.platformViewRegistry.registerViewFactory( + "interactive_media_ads.packages.flutter.dev/view", + FlutterViewFactory(registrar.instanceManager)) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) + registrar.tearDown() + registrar.instanceManager.clear() + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + registrar.context = binding.activity + } + + override fun onDetachedFromActivityForConfigChanges() { + registrar.context = pluginBinding.applicationContext + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + registrar.context = binding.activity + } + + override fun onDetachedFromActivity() { + registrar.context = pluginBinding.applicationContext + } +} + +internal class FlutterViewFactory(private val instanceManager: PigeonInstanceManager) : + PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + override fun create(context: Context, viewId: Int, args: Any?): PlatformView { + val identifier = + args as Int? + ?: throw IllegalStateException("An identifier is required to retrieve a View instance.") + val instance: Any? = instanceManager.getInstance(identifier.toLong()) + if (instance is PlatformView) { + return instance + } else if (instance is View) { + return object : PlatformView { + override fun getView(): View { + return instance + } + + override fun dispose() {} + } + } + throw IllegalStateException("Unable to find a PlatformView or View instance: $args, $instance") } } diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/MediaPlayerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/MediaPlayerProxyApi.kt new file mode 100644 index 00000000000..61915a2bc03 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/MediaPlayerProxyApi.kt @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.media.MediaPlayer + +/** + * ProxyApi implementation for [MediaPlayer]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class MediaPlayerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiMediaPlayer(pigeonRegistrar) { + override fun getDuration(pigeon_instance: MediaPlayer): Long { + return pigeon_instance.duration.toLong() + } + + override fun seekTo(pigeon_instance: MediaPlayer, mSec: Long) { + pigeon_instance.seekTo(mSec.toInt()) + } + + override fun start(pigeon_instance: MediaPlayer) { + pigeon_instance.start() + } + + override fun pause(pigeon_instance: MediaPlayer) { + pigeon_instance.pause() + } + + override fun stop(pigeon_instance: MediaPlayer) { + pigeon_instance.stop() + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ProxyApiRegistrar.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ProxyApiRegistrar.kt new file mode 100644 index 00000000000..ee15a91c2f3 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ProxyApiRegistrar.kt @@ -0,0 +1,128 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.content.Context +import android.os.Handler +import android.os.Looper +import io.flutter.plugin.common.BinaryMessenger + +/** + * Implementation of [PigeonProxyApiRegistrar] that provides each ProxyApi implementation and any + * additional resources needed by an implementation. + */ +open class ProxyApiRegistrar(binaryMessenger: BinaryMessenger, var context: Context) : + PigeonProxyApiRegistrar(binaryMessenger) { + + // Added to be overriden for tests. The test implementation calls `callback` immediately, instead + // of waiting for the main thread to run it. + internal open fun runOnMainThread(callback: Runnable) { + Handler(Looper.getMainLooper()).post { callback.run() } + } + + override fun getPigeonApiBaseDisplayContainer(): PigeonApiBaseDisplayContainer { + return BaseDisplayContainerProxyApi(this) + } + + override fun getPigeonApiAdDisplayContainer(): PigeonApiAdDisplayContainer { + return AdDisplayContainerProxyApi(this) + } + + override fun getPigeonApiAdsLoader(): PigeonApiAdsLoader { + return AdsLoaderProxyApi(this) + } + + override fun getPigeonApiAdsManagerLoadedEvent(): PigeonApiAdsManagerLoadedEvent { + return AdsManagerLoadedEventProxyApi(this) + } + + override fun getPigeonApiAdsLoadedListener(): PigeonApiAdsLoadedListener { + return AdsLoadedListenerProxyApi(this) + } + + override fun getPigeonApiAdErrorListener(): PigeonApiAdErrorListener { + return AdErrorListenerProxyApi(this) + } + + override fun getPigeonApiAdErrorEvent(): PigeonApiAdErrorEvent { + return AdErrorEventProxyApi(this) + } + + override fun getPigeonApiAdError(): PigeonApiAdError { + return AdErrorProxyApi(this) + } + + override fun getPigeonApiAdsRequest(): PigeonApiAdsRequest { + return AdsRequestProxyApi(this) + } + + override fun getPigeonApiContentProgressProvider(): PigeonApiContentProgressProvider { + return ContentProgressProviderProxyApi(this) + } + + override fun getPigeonApiAdsManager(): PigeonApiAdsManager { + return AdsManagerProxyApi(this) + } + + override fun getPigeonApiBaseManager(): PigeonApiBaseManager { + return BaseManagerProxyApi(this) + } + + override fun getPigeonApiAdEventListener(): PigeonApiAdEventListener { + return AdEventListenerProxyApi(this) + } + + override fun getPigeonApiAdEvent(): PigeonApiAdEvent { + return AdEventProxyApi(this) + } + + override fun getPigeonApiImaSdkFactory(): PigeonApiImaSdkFactory { + return ImaSdkFactoryProxyApi(this) + } + + override fun getPigeonApiImaSdkSettings(): PigeonApiImaSdkSettings { + return ImaSdkSettingsProxyApi(this) + } + + override fun getPigeonApiVideoAdPlayer(): PigeonApiVideoAdPlayer { + return VideoAdPlayerProxyApi(this) + } + + override fun getPigeonApiVideoProgressUpdate(): PigeonApiVideoProgressUpdate { + return VideoProgressUpdateProxyApi(this) + } + + override fun getPigeonApiVideoAdPlayerCallback(): PigeonApiVideoAdPlayerCallback { + return VideoAdPlayerCallbackProxyApi(this) + } + + override fun getPigeonApiAdMediaInfo(): PigeonApiAdMediaInfo { + return AdMediaInfoProxyApi(this) + } + + override fun getPigeonApiAdPodInfo(): PigeonApiAdPodInfo { + return AdPodInfoProxyApi(this) + } + + override fun getPigeonApiFrameLayout(): PigeonApiFrameLayout { + return FrameLayoutProxyApi(this) + } + + override fun getPigeonApiViewGroup(): PigeonApiViewGroup { + return ViewGroupProxyApi(this) + } + + override fun getPigeonApiVideoView(): PigeonApiVideoView { + return VideoViewProxyApi(this) + } + + override fun getPigeonApiView(): PigeonApiView { + return ViewProxyApi(this) + } + + override fun getPigeonApiMediaPlayer(): PigeonApiMediaPlayer { + return MediaPlayerProxyApi(this) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerCallbackProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerCallbackProxyApi.kt new file mode 100644 index 00000000000..dc10cbd7753 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerCallbackProxyApi.kt @@ -0,0 +1,87 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo +import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer +import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + +/** + * ProxyApi implementation for [VideoAdPlayer.VideoAdPlayerCallback]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class VideoAdPlayerCallbackProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiVideoAdPlayerCallback(pigeonRegistrar) { + override fun onAdProgress( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo, + videoProgressUpdate: VideoProgressUpdate + ) { + pigeon_instance.onAdProgress(adMediaInfo, videoProgressUpdate) + } + + override fun onBuffering( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onBuffering(adMediaInfo) + } + + override fun onContentComplete(pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback) { + pigeon_instance.onContentComplete() + } + + override fun onEnded( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onEnded(adMediaInfo) + } + + override fun onError( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onError(adMediaInfo) + } + + override fun onLoaded( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onLoaded(adMediaInfo) + } + + override fun onPause( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onPause(adMediaInfo) + } + + override fun onPlay( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onPlay(adMediaInfo) + } + + override fun onResume( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo + ) { + pigeon_instance.onResume(adMediaInfo) + } + + override fun onVolumeChanged( + pigeon_instance: VideoAdPlayer.VideoAdPlayerCallback, + adMediaInfo: AdMediaInfo, + percentage: Long + ) { + pigeon_instance.onVolumeChanged(adMediaInfo, percentage.toInt()) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerProxyApi.kt new file mode 100644 index 00000000000..5c79153bac7 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerProxyApi.kt @@ -0,0 +1,80 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdPodInfo +import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo +import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer +import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + +/** + * ProxyApi implementation for [VideoAdPlayer]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class VideoAdPlayerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiVideoAdPlayer(pigeonRegistrar) { + override fun pigeon_defaultConstructor(): VideoAdPlayer { + return VideoAdPlayerImpl(this) + } + + internal class VideoAdPlayerImpl(val api: VideoAdPlayerProxyApi) : VideoAdPlayer { + var savedVolume: Int = 0 + + var savedAdProgress: VideoProgressUpdate = VideoProgressUpdate.VIDEO_TIME_NOT_READY + + override fun getAdProgress(): VideoProgressUpdate { + return savedAdProgress + } + + override fun getVolume(): Int { + return savedVolume + } + + override fun addCallback(callback: VideoAdPlayer.VideoAdPlayerCallback) { + api.pigeonRegistrar.runOnMainThread { api.addCallback(this, callbackArg = callback) {} } + } + + override fun loadAd(adMediaInfo: AdMediaInfo, adPodInfo: AdPodInfo) { + api.pigeonRegistrar.runOnMainThread { api.loadAd(this, adMediaInfo, adPodInfo) {} } + } + + override fun pauseAd(adMediaInfo: AdMediaInfo) { + api.pigeonRegistrar.runOnMainThread { api.pauseAd(this, adMediaInfo) {} } + } + + override fun playAd(adMediaInfo: AdMediaInfo) { + api.pigeonRegistrar.runOnMainThread { api.playAd(this, adMediaInfo) {} } + } + + override fun release() { + api.pigeonRegistrar.runOnMainThread { api.release(this) {} } + } + + override fun removeCallback(callback: VideoAdPlayer.VideoAdPlayerCallback) { + api.pigeonRegistrar.runOnMainThread { api.removeCallback(this, callbackArg = callback) {} } + } + + override fun stopAd(adMediaInfo: AdMediaInfo) { + api.pigeonRegistrar.runOnMainThread { api.stopAd(this, adMediaInfo) {} } + } + } + + /** + * Sets the internal `volume` variable that is returned in the [VideoAdPlayer.getVolume] callback. + */ + override fun setVolume(pigeon_instance: VideoAdPlayer, value: Long) { + (pigeon_instance as VideoAdPlayerImpl).savedVolume = value.toInt() + } + + /** + * Sets the internal `adProgress` variable that is returned in the [VideoAdPlayer.getAdProgress] + * callback. + */ + override fun setAdProgress(pigeon_instance: VideoAdPlayer, progress: VideoProgressUpdate) { + (pigeon_instance as VideoAdPlayerImpl).savedAdProgress = progress + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoProgressUpdateProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoProgressUpdateProxyApi.kt new file mode 100644 index 00000000000..307bf6ec3f3 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoProgressUpdateProxyApi.kt @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate + +/** + * ProxyApi implementation for [VideoProgressUpdate]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class VideoProgressUpdateProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiVideoProgressUpdate(pigeonRegistrar) { + override fun pigeon_defaultConstructor( + currentTimeMs: Long, + durationMs: Long + ): VideoProgressUpdate { + return VideoProgressUpdate(currentTimeMs, durationMs) + } + + override fun videoTimeNotReady(): VideoProgressUpdate { + return VideoProgressUpdate.VIDEO_TIME_NOT_READY + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoViewProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoViewProxyApi.kt new file mode 100644 index 00000000000..983f97e7f77 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/VideoViewProxyApi.kt @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.media.MediaPlayer +import android.net.Uri +import android.widget.VideoView + +/** + * ProxyApi implementation for [VideoView]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class VideoViewProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiVideoView(pigeonRegistrar) { + + override fun pigeon_defaultConstructor(): VideoView { + val instance = VideoView(pigeonRegistrar.context) + instance.setOnPreparedListener { player: MediaPlayer -> onPrepared(instance, player) {} } + instance.setOnErrorListener { player: MediaPlayer, what: Int, extra: Int -> + onError(instance, player, what.toLong(), extra.toLong()) {} + true + } + instance.setOnCompletionListener { player: MediaPlayer -> onCompletion(instance, player) {} } + return instance + } + + override fun setVideoUri(pigeon_instance: VideoView, uri: String) { + pigeon_instance.setVideoURI(Uri.parse(uri)) + } + + override fun getCurrentPosition(pigeon_instance: VideoView): Long { + return pigeon_instance.currentPosition.toLong() + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ViewGroupProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ViewGroupProxyApi.kt new file mode 100644 index 00000000000..5ba9fa8d58f --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ViewGroupProxyApi.kt @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.view.View +import android.view.ViewGroup + +/** + * ProxyApi implementation for [ViewGroup]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class ViewGroupProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiViewGroup(pigeonRegistrar) { + override fun addView(pigeon_instance: ViewGroup, view: View) { + pigeon_instance.addView(view) + } +} diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ViewProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ViewProxyApi.kt new file mode 100644 index 00000000000..59934e88225 --- /dev/null +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/ViewProxyApi.kt @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +/** + * ProxyApi implementation for [android.view.View]. + * + *

This class may handle instantiating native object instances that are attached to a Dart + * instance or handle method calls on the associated native class or an instance of that class. + */ +class ViewProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : + PigeonApiView(pigeonRegistrar) diff --git a/packages/interactive_media_ads/android/src/test/kotlin/android/net/Uri.kt b/packages/interactive_media_ads/android/src/test/kotlin/android/net/Uri.kt new file mode 100644 index 00000000000..107eef275d9 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/android/net/Uri.kt @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package android.net + +/** + * Redeclaration of Uri that works for tests. + * + * Without this redeclaration, `Uri.parse` always returns null. + */ +class Uri { + companion object { + @JvmStatic var lastValue: String? = null + + @JvmStatic + fun parse(value: String): Uri { + lastValue = value + return Uri() + } + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorEventProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorEventProxyApiTest.kt new file mode 100644 index 00000000000..e51f4f66894 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorEventProxyApiTest.kt @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdError +import com.google.ads.interactivemedia.v3.api.AdErrorEvent +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito.mock +import org.mockito.kotlin.whenever + +internal class AdErrorEventProxyApiTest { + @Test + fun error() { + val api = TestProxyApiRegistrar().getPigeonApiAdErrorEvent() + + val instance = mock() + val mockError = mock() + whenever(instance.error).thenReturn(mockError) + + assertEquals(mockError, api.error(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorListenerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorListenerProxyApiTest.kt new file mode 100644 index 00000000000..10890f84d61 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorListenerProxyApiTest.kt @@ -0,0 +1,35 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdErrorEvent +import kotlin.test.Test +import kotlin.test.assertTrue +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever + +internal class AdErrorListenerProxyApiTest { + @Test + fun pigeon_defaultConstructor() { + val api = TestProxyApiRegistrar().getPigeonApiAdErrorListener() + + assertTrue(api.pigeon_defaultConstructor() is AdErrorListenerProxyApi.AdErrorListenerImpl) + } + + @Test + fun onAdError() { + val mockApi = mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = AdErrorListenerProxyApi.AdErrorListenerImpl(mockApi) + val mockEvent = mock() + instance.onAdError(mockEvent) + + verify(mockApi).onAdError(eq(instance), eq(mockEvent), any()) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorProxyApiTest.kt new file mode 100644 index 00000000000..62e8efe927f --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdErrorProxyApiTest.kt @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdError +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito +import org.mockito.kotlin.whenever + +class AdErrorProxyApiTest { + @Test + fun errorCode() { + val api = TestProxyApiRegistrar().getPigeonApiAdError() + + val instance = Mockito.mock() + whenever(instance.errorCode).thenReturn(AdError.AdErrorCode.VIDEO_PLAY_ERROR) + + assertEquals(AdErrorCode.VIDEO_PLAY_ERROR, api.errorCode(instance)) + } + + @Test + fun errorCodeNumber() { + val api = TestProxyApiRegistrar().getPigeonApiAdError() + + val instance = Mockito.mock() + whenever(instance.errorCodeNumber).thenReturn(0) + + assertEquals(0, api.errorCodeNumber(instance)) + } + + @Test + fun errorType() { + val api = TestProxyApiRegistrar().getPigeonApiAdError() + + val instance = Mockito.mock() + whenever(instance.errorType).thenReturn(AdError.AdErrorType.LOAD) + + assertEquals(AdErrorType.LOAD, api.errorType(instance)) + } + + @Test + fun message() { + val api = TestProxyApiRegistrar().getPigeonApiAdError() + + val instance = Mockito.mock() + whenever(instance.message).thenReturn("message") + + assertEquals("message", api.message(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdEventListenerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdEventListenerProxyApiTest.kt new file mode 100644 index 00000000000..02904a9005b --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdEventListenerProxyApiTest.kt @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdEvent +import kotlin.test.Test +import kotlin.test.assertTrue +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever + +class AdEventListenerProxyApiTest { + @Test + fun pigeon_defaultConstructor() { + val api = TestProxyApiRegistrar().getPigeonApiAdEventListener() + + assertTrue(api.pigeon_defaultConstructor() is AdEventListenerProxyApi.AdEventListenerImpl) + } + + @Test + fun onAdEvent() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = AdEventListenerProxyApi.AdEventListenerImpl(mockApi) + val mockEvent = Mockito.mock() + instance.onAdEvent(mockEvent) + + Mockito.verify(mockApi).onAdEvent(eq(instance), eq(mockEvent), any()) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdEventProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdEventProxyApiTest.kt new file mode 100644 index 00000000000..fbab52be008 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdEventProxyApiTest.kt @@ -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. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdEvent +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito +import org.mockito.kotlin.whenever + +class AdEventProxyApiTest { + @Test + fun type() { + val api = TestProxyApiRegistrar().getPigeonApiAdEvent() + + val instance = Mockito.mock() + whenever(instance.type).thenReturn(AdEvent.AdEventType.PAUSED) + + assertEquals(AdEventType.PAUSED, api.type(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdMediaInfoProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdMediaInfoProxyApiTest.kt new file mode 100644 index 00000000000..37602d5ab9e --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdMediaInfoProxyApiTest.kt @@ -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. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito +import org.mockito.kotlin.whenever + +class AdMediaInfoProxyApiTest { + @Test + fun url() { + val api = TestProxyApiRegistrar().getPigeonApiAdMediaInfo() + + val instance = Mockito.mock() + whenever(instance.url).thenReturn("url") + + assertEquals("url", api.url(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdPodInfoProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdPodInfoProxyApiTest.kt new file mode 100644 index 00000000000..b1610f25108 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdPodInfoProxyApiTest.kt @@ -0,0 +1,73 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdPodInfo +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito +import org.mockito.kotlin.whenever + +class AdPodInfoProxyApiTest { + @Test + fun adPosition() { + val api = TestProxyApiRegistrar().getPigeonApiAdPodInfo() + + val instance = Mockito.mock() + whenever(instance.adPosition).thenReturn(0) + + assertEquals(0, api.adPosition(instance)) + } + + @Test + fun maxDuration() { + val api = TestProxyApiRegistrar().getPigeonApiAdPodInfo() + + val instance = Mockito.mock() + whenever(instance.maxDuration).thenReturn(0.0) + + assertEquals(0.0, api.maxDuration(instance)) + } + + @Test + fun podIndex() { + val api = TestProxyApiRegistrar().getPigeonApiAdPodInfo() + + val instance = Mockito.mock() + whenever(instance.podIndex).thenReturn(0) + + assertEquals(0, api.podIndex(instance)) + } + + @Test + fun timeOffset() { + val api = TestProxyApiRegistrar().getPigeonApiAdPodInfo() + + val instance = Mockito.mock() + whenever(instance.timeOffset).thenReturn(0.0) + + assertEquals(0.0, api.timeOffset(instance)) + } + + @Test + fun totalAds() { + val api = TestProxyApiRegistrar().getPigeonApiAdPodInfo() + + val instance = Mockito.mock() + whenever(instance.totalAds).thenReturn(0) + + assertEquals(0, api.totalAds(instance)) + } + + @Test + fun isBumper() { + val api = TestProxyApiRegistrar().getPigeonApiAdPodInfo() + + val instance = Mockito.mock() + whenever(instance.isBumper).thenReturn(true) + + assertEquals(true, api.isBumper(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoadedListenerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoadedListenerProxyApiTest.kt new file mode 100644 index 00000000000..5d89ba05da3 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoadedListenerProxyApiTest.kt @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent +import kotlin.test.Test +import kotlin.test.assertTrue +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever + +class AdsLoadedListenerProxyApiTest { + @Test + fun pigeon_defaultConstructor() { + val api = TestProxyApiRegistrar().getPigeonApiAdsLoadedListener() + + assertTrue(api.pigeon_defaultConstructor() is AdsLoadedListenerProxyApi.AdsLoadedListenerImpl) + } + + @Test + fun onAdsManagerLoaded() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = AdsLoadedListenerProxyApi.AdsLoadedListenerImpl(mockApi) + val mockEvent = Mockito.mock() + instance.onAdsManagerLoaded(mockEvent) + + Mockito.verify(mockApi).onAdsManagerLoaded(eq(instance), eq(mockEvent), any()) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoaderProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoaderProxyApiTest.kt new file mode 100644 index 00000000000..f70fb27f00b --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsLoaderProxyApiTest.kt @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdErrorEvent +import com.google.ads.interactivemedia.v3.api.AdsLoader +import com.google.ads.interactivemedia.v3.api.AdsRequest +import kotlin.test.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class AdsLoaderProxyApiTest { + @Test + fun addAdErrorListener() { + val api = TestProxyApiRegistrar().getPigeonApiAdsLoader() + + val instance = mock() + val mockListener = mock() + api.addAdErrorListener(instance, mockListener) + + verify(instance).addAdErrorListener(mockListener) + } + + @Test + fun addAdsLoadedListener() { + val api = TestProxyApiRegistrar().getPigeonApiAdsLoader() + + val instance = mock() + val mockListener = mock() + api.addAdsLoadedListener(instance, mockListener) + + verify(instance).addAdsLoadedListener(mockListener) + } + + @Test + fun requestAds() { + val api = TestProxyApiRegistrar().getPigeonApiAdsLoader() + + val instance = mock() + val mockRequest = mock() + api.requestAds(instance, mockRequest) + + verify(instance).requestAds(mockRequest) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerLoadedEventProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerLoadedEventProxyApiTest.kt new file mode 100644 index 00000000000..ff3f3f51dce --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerLoadedEventProxyApiTest.kt @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsManager +import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.Mockito +import org.mockito.kotlin.whenever + +class AdsManagerLoadedEventProxyApiTest { + @Test + fun manager() { + val api = TestProxyApiRegistrar().getPigeonApiAdsManagerLoadedEvent() + + val instance = Mockito.mock() + val mockManager = Mockito.mock() + whenever(instance.adsManager).thenReturn(mockManager) + + assertEquals(mockManager, api.manager(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerProxyApiTest.kt new file mode 100644 index 00000000000..8dc78843ef7 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsManagerProxyApiTest.kt @@ -0,0 +1,42 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsManager +import kotlin.test.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class AdsManagerProxyApiTest { + @Test + fun discardAdBreak() { + val api = TestProxyApiRegistrar().getPigeonApiAdsManager() + + val instance = mock() + api.discardAdBreak(instance) + + verify(instance).discardAdBreak() + } + + @Test + fun pause() { + val api = TestProxyApiRegistrar().getPigeonApiAdsManager() + + val instance = mock() + api.pause(instance) + + verify(instance).pause() + } + + @Test + fun start() { + val api = TestProxyApiRegistrar().getPigeonApiAdsManager() + + val instance = mock() + api.start(instance) + + verify(instance).start() + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApiTest.kt new file mode 100644 index 00000000000..66c3d35ee55 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApiTest.kt @@ -0,0 +1,35 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdsRequest +import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider +import kotlin.test.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class AdsRequestProxyApiTest { + @Test + fun setAdTagUrl() { + val api = TestProxyApiRegistrar().getPigeonApiAdsRequest() + + val instance = mock() + api.setAdTagUrl(instance, "adTag") + + verify(instance).adTagUrl = + "adTag&request_agent=Flutter-IMA-${AdsRequestProxyApi.pluginVersion}" + } + + @Test + fun setContentProgressProvider() { + val api = TestProxyApiRegistrar().getPigeonApiAdsRequest() + + val instance = mock() + val mockProvider = mock() + api.setContentProgressProvider(instance, mockProvider) + + verify(instance).contentProgressProvider = mockProvider + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/BaseManagerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/BaseManagerProxyApiTest.kt new file mode 100644 index 00000000000..0c9293d8d71 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/BaseManagerProxyApiTest.kt @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdErrorEvent +import com.google.ads.interactivemedia.v3.api.AdEvent +import com.google.ads.interactivemedia.v3.api.BaseManager +import kotlin.test.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class BaseManagerProxyApiTest { + @Test + fun addAdErrorListener() { + val api = TestProxyApiRegistrar().getPigeonApiBaseManager() + + val instance = mock() + val mockListener = mock() + api.addAdErrorListener(instance, mockListener) + + verify(instance).addAdErrorListener(mockListener) + } + + @Test + fun addAdEventListener() { + val api = TestProxyApiRegistrar().getPigeonApiBaseManager() + + val instance = mock() + val mockListener = mock() + api.addAdEventListener(instance, mockListener) + + verify(instance).addAdEventListener(mockListener) + } + + @Test + fun destroy() { + val api = TestProxyApiRegistrar().getPigeonApiBaseManager() + + val instance = mock() + api.destroy(instance) + + verify(instance).destroy() + } + + @Test + fun init() { + val api = TestProxyApiRegistrar().getPigeonApiBaseManager() + + val instance = mock() + api.init(instance) + + verify(instance).init() + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkFactoryProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkFactoryProxyApiTest.kt new file mode 100644 index 00000000000..600e5e41a09 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/ImaSdkFactoryProxyApiTest.kt @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdDisplayContainer +import com.google.ads.interactivemedia.v3.api.AdsLoader +import com.google.ads.interactivemedia.v3.api.AdsRequest +import com.google.ads.interactivemedia.v3.api.ImaSdkFactory +import com.google.ads.interactivemedia.v3.api.ImaSdkSettings +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class ImaSdkFactoryProxyApiTest { + @Test + fun createImaSdkSettings() { + val api = TestProxyApiRegistrar().getPigeonApiImaSdkFactory() + + val instance = mock() + val mockSettings = mock() + whenever(instance.createImaSdkSettings()).thenReturn(mockSettings) + + assertEquals(mockSettings, api.createImaSdkSettings(instance)) + } + + @Test + fun createAdsLoader() { + val registrar = TestProxyApiRegistrar() + val api = registrar.getPigeonApiImaSdkFactory() + + val instance = mock() + val mockAdsLoader = mock() + val mockSettings = mock() + val mockContainer = mock() + whenever(instance.createAdsLoader(registrar.context, mockSettings, mockContainer)) + .thenReturn(mockAdsLoader) + + assertEquals(mockAdsLoader, api.createAdsLoader(instance, mockSettings, mockContainer)) + } + + @Test + fun createAdsRequest() { + val api = TestProxyApiRegistrar().getPigeonApiImaSdkFactory() + + val instance = mock() + val mockRequest = mock() + whenever(instance.createAdsRequest()).thenReturn(mockRequest) + + assertEquals(mockRequest, api.createAdsRequest(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPluginTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPluginTest.kt deleted file mode 100644 index 3adc0d0a56b..00000000000 --- a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsPluginTest.kt +++ /dev/null @@ -1,31 +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. - -package dev.flutter.packages.interactive_media_ads - -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import kotlin.test.Test -import org.mockito.Mockito - -/* - * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. - * - * Once you have built the plugin's example app, you can run these tests from the command - * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or - * you can run them directly from IDEs that support JUnit such as Android Studio. - */ - -internal class InteractiveMediaAdsPluginTest { - @Test - fun onMethodCall_getPlatformVersion_returnsExpectedValue() { - val plugin = InteractiveMediaAdsPlugin() - - val call = MethodCall("getPlatformVersion", null) - val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) - plugin.onMethodCall(call, mockResult) - - Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) - } -} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/MediaPlayerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/MediaPlayerProxyApiTest.kt new file mode 100644 index 00000000000..cee45eb3d90 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/MediaPlayerProxyApiTest.kt @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.media.MediaPlayer +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class MediaPlayerProxyApiTest { + @Test + fun getDuration() { + val api = TestProxyApiRegistrar().getPigeonApiMediaPlayer() + + val instance = mock() + whenever(instance.duration).thenReturn(0) + + assertEquals(0, api.getDuration(instance)) + } + + @Test + fun seekTo() { + val api = TestProxyApiRegistrar().getPigeonApiMediaPlayer() + + val instance = mock() + api.seekTo(instance, 0) + + verify(instance).seekTo(0) + } + + @Test + fun start() { + val api = TestProxyApiRegistrar().getPigeonApiMediaPlayer() + + val instance = mock() + api.start(instance) + + verify(instance).start() + } + + @Test + fun pause() { + val api = TestProxyApiRegistrar().getPigeonApiMediaPlayer() + + val instance = mock() + api.pause(instance) + + verify(instance).pause() + } + + @Test + fun stop() { + val api = TestProxyApiRegistrar().getPigeonApiMediaPlayer() + + val instance = mock() + api.stop(instance) + + verify(instance).stop() + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/TestProxyApiRegistrar.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/TestProxyApiRegistrar.kt new file mode 100644 index 00000000000..0aba7066a28 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/TestProxyApiRegistrar.kt @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import org.mockito.kotlin.mock + +/** + * Test implementation of `ProxyApiRegistrar` that provides mocks and instantly runs callbacks + * instead of posting them. + */ +class TestProxyApiRegistrar : ProxyApiRegistrar(mock(), mock()) { + override fun runOnMainThread(callback: Runnable) { + callback.run() + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerCallbackProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerCallbackProxyApiTest.kt new file mode 100644 index 00000000000..125fd16f951 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerCallbackProxyApiTest.kt @@ -0,0 +1,124 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo +import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback +import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate +import kotlin.test.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class VideoAdPlayerCallbackProxyApiTest { + @Test + fun onAdProgress() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + val mockUpdate = mock() + api.onAdProgress(instance, mockInfo, mockUpdate) + + verify(instance).onAdProgress(mockInfo, mockUpdate) + } + + @Test + fun onBuffering() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onBuffering(instance, mockInfo) + + verify(instance).onBuffering(mockInfo) + } + + @Test + fun onContentComplete() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + api.onContentComplete(instance) + + verify(instance).onContentComplete() + } + + @Test + fun onEnded() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onEnded(instance, mockInfo) + + verify(instance).onEnded(mockInfo) + } + + @Test + fun onError() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onError(instance, mockInfo) + + verify(instance).onError(mockInfo) + } + + @Test + fun onLoaded() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onLoaded(instance, mockInfo) + + verify(instance).onLoaded(mockInfo) + } + + @Test + fun onPause() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onPause(instance, mockInfo) + + verify(instance).onPause(mockInfo) + } + + @Test + fun onPlay() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onPlay(instance, mockInfo) + + verify(instance).onPlay(mockInfo) + } + + @Test + fun onResume() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onResume(instance, mockInfo) + + verify(instance).onResume(mockInfo) + } + + @Test + fun onVolumeChanged() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayerCallback() + + val instance = mock() + val mockInfo = mock() + api.onVolumeChanged(instance, mockInfo, 0) + + verify(instance).onVolumeChanged(mockInfo, 0) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerProxyApiTest.kt new file mode 100644 index 00000000000..01eb2b1f6dd --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoAdPlayerProxyApiTest.kt @@ -0,0 +1,132 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import com.google.ads.interactivemedia.v3.api.AdPodInfo +import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo +import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer +import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class VideoAdPlayerProxyApiTest { + @Test + fun pigeon_defaultConstructor() { + val api = ProxyApiRegistrar(Mockito.mock(), Mockito.mock()).getPigeonApiVideoAdPlayer() + + assertTrue(api.pigeon_defaultConstructor() is VideoAdPlayerProxyApi.VideoAdPlayerImpl) + } + + @Test + fun setVolume() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayer() + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(api as VideoAdPlayerProxyApi) + api.setVolume(instance, 0) + + assertEquals(0, instance.volume) + } + + @Test + fun setAdProgress() { + val api = TestProxyApiRegistrar().getPigeonApiVideoAdPlayer() + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(api as VideoAdPlayerProxyApi) + val mockProgressUpdate = mock() + api.setAdProgress(instance, mockProgressUpdate) + + assertEquals(mockProgressUpdate, instance.adProgress) + } + + @Test + fun addCallback() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + val mockCallback = Mockito.mock() + instance.addCallback(mockCallback) + + Mockito.verify(mockApi).addCallback(eq(instance), eq(mockCallback), any()) + } + + @Test + fun loadAd() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + val mockMediaInfo = mock() + val mockPodInfo = mock() + instance.loadAd(mockMediaInfo, mockPodInfo) + + Mockito.verify(mockApi).loadAd(eq(instance), eq(mockMediaInfo), eq(mockPodInfo), any()) + } + + @Test + fun pauseAd() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + val mockMediaInfo = mock() + instance.pauseAd(mockMediaInfo) + + Mockito.verify(mockApi).pauseAd(eq(instance), eq(mockMediaInfo), any()) + } + + @Test + fun playAd() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + val mockMediaInfo = mock() + instance.playAd(mockMediaInfo) + + Mockito.verify(mockApi).playAd(eq(instance), eq(mockMediaInfo), any()) + } + + @Test + fun release() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + instance.release() + + Mockito.verify(mockApi).release(eq(instance), any()) + } + + @Test + fun removeCallback() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + val mockCallback = Mockito.mock() + instance.removeCallback(mockCallback) + + Mockito.verify(mockApi).removeCallback(eq(instance), eq(mockCallback), any()) + } + + @Test + fun stopAd() { + val mockApi = Mockito.mock() + whenever(mockApi.pigeonRegistrar).thenReturn(TestProxyApiRegistrar()) + + val instance = VideoAdPlayerProxyApi.VideoAdPlayerImpl(mockApi) + val mockMediaInfo = mock() + instance.stopAd(mockMediaInfo) + + Mockito.verify(mockApi).stopAd(eq(instance), eq(mockMediaInfo), any()) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoViewProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoViewProxyApiTest.kt new file mode 100644 index 00000000000..f43af0aa428 --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/VideoViewProxyApiTest.kt @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.net.Uri +import android.widget.VideoView +import kotlin.test.Test +import kotlin.test.assertEquals +import org.mockito.kotlin.isNotNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class VideoViewProxyApiTest { + @Test + fun setVideoURI() { + val api = TestProxyApiRegistrar().getPigeonApiVideoView() + + val instance = mock() + api.setVideoUri(instance, "adTag") + + verify(instance).setVideoURI(isNotNull()) + assertEquals("adTag", Uri.lastValue) + } + + @Test + fun getCurrentPosition() { + val api = TestProxyApiRegistrar().getPigeonApiVideoView() + + val instance = mock() + whenever(instance.currentPosition).thenReturn(0) + api.getCurrentPosition(instance) + + assertEquals(0, api.getCurrentPosition(instance)) + } +} diff --git a/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/ViewGroupProxyApiTest.kt b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/ViewGroupProxyApiTest.kt new file mode 100644 index 00000000000..e73e29ed8ea --- /dev/null +++ b/packages/interactive_media_ads/android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/ViewGroupProxyApiTest.kt @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.interactive_media_ads + +import android.view.View +import android.view.ViewGroup +import kotlin.test.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class ViewGroupProxyApiTest { + @Test + fun addView() { + val api = TestProxyApiRegistrar().getPigeonApiViewGroup() + + val instance = mock() + val mockView = mock() + api.addView(instance, mockView) + + verify(instance).addView(mockView) + } +} diff --git a/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml b/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml index 8733f2b862a..48c5bc7ca18 100644 --- a/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml +++ b/packages/interactive_media_ads/example/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,9 @@ + + + + + createState() => _MyAppState(); + State createState() => _AdExampleWidgetState(); } -class _MyAppState extends State { +class _AdExampleWidgetState extends State { + // IMA sample tag for a single skippable inline video ad. See more IMA sample + // tags at https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags + static const String _adTagUrl = + 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator='; + + // The AdsLoader instance exposes the request ads method. + late final AdsLoader _adsLoader; + + // AdsManager exposes methods to control ad playback and listen to ad events. + AdsManager? _adsManager; + + // Whether the widget should be displaying the content video. The content + // player is hidden while Ads are playing. + bool _shouldShowContentVideo = true; + + // Controls the content video player. + late final VideoPlayerController _contentVideoController; + // #enddocregion example_widget + + // #docregion ad_and_content_players + late final AdDisplayContainer _adDisplayContainer = AdDisplayContainer( + onContainerAdded: (AdDisplayContainer container) { + // Ads can't be requested until the `AdDisplayContainer` has been added to + // the native View hierarchy. + _requestAds(container); + }, + ); + + @override + void initState() { + super.initState(); + _contentVideoController = VideoPlayerController.networkUrl( + Uri.parse( + 'https://storage.googleapis.com/gvabox/media/samples/stock.mp4', + ), + ) + ..addListener(() { + if (_contentVideoController.value.isCompleted) { + _adsLoader.contentComplete(); + setState(() {}); + } + }) + ..initialize().then((_) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + setState(() {}); + }); + } + // #enddocregion ad_and_content_players + + // #docregion request_ads + Future _requestAds(AdDisplayContainer container) { + _adsLoader = AdsLoader( + container: container, + onAdsLoaded: (OnAdsLoadedData data) { + final AdsManager manager = data.manager; + _adsManager = data.manager; + + manager.setAdsManagerDelegate(AdsManagerDelegate( + onAdEvent: (AdEvent event) { + debugPrint('OnAdEvent: ${event.type}'); + switch (event.type) { + case AdEventType.loaded: + manager.start(); + case AdEventType.contentPauseRequested: + _pauseContent(); + case AdEventType.contentResumeRequested: + _resumeContent(); + case AdEventType.allAdsCompleted: + manager.destroy(); + _adsManager = null; + case AdEventType.clicked: + case AdEventType.complete: + } + }, + onAdErrorEvent: (AdErrorEvent event) { + debugPrint('AdErrorEvent: ${event.error.message}'); + _resumeContent(); + }, + )); + + manager.init(); + }, + onAdsLoadError: (AdsLoadErrorData data) { + debugPrint('OnAdsLoadError: ${data.error.message}'); + _resumeContent(); + }, + ); + + return _adsLoader.requestAds(AdsRequest(adTagUrl: _adTagUrl)); + } + + Future _resumeContent() { + setState(() { + _shouldShowContentVideo = true; + }); + return _contentVideoController.play(); + } + + Future _pauseContent() { + setState(() { + _shouldShowContentVideo = false; + }); + return _contentVideoController.pause(); + } + // #enddocregion request_ads + + // #docregion dispose + @override + void dispose() { + super.dispose(); + _contentVideoController.dispose(); + _adsManager?.destroy(); + } + // #enddocregion dispose + + // #docregion example_widget + // #docregion widget_build @override Widget build(BuildContext context) { - debugPrint('THEAPP'); - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Running on: $defaultTargetPlatform'), + // #enddocregion example_widget + return Scaffold( + body: Center( + child: SizedBox( + width: 300, + child: !_contentVideoController.value.isInitialized + ? Container() + : AspectRatio( + aspectRatio: _contentVideoController.value.aspectRatio, + child: Stack( + children: [ + // The display container must be on screen before any Ads can be + // loaded and can't be removed between ads. This handles clicks for + // ads. + _adDisplayContainer, + if (_shouldShowContentVideo) + VideoPlayer(_contentVideoController) + ], + ), + ), ), ), + floatingActionButton: + _contentVideoController.value.isInitialized && _shouldShowContentVideo + ? FloatingActionButton( + onPressed: () { + setState(() { + _contentVideoController.value.isPlaying + ? _contentVideoController.pause() + : _contentVideoController.play(); + }); + }, + child: Icon( + _contentVideoController.value.isPlaying + ? Icons.pause + : Icons.play_arrow, + ), + ) + : null, ); + // #docregion example_widget } + // #enddocregion widget_build } +// #enddocregion example_widget diff --git a/packages/interactive_media_ads/example/pubspec.yaml b/packages/interactive_media_ads/example/pubspec.yaml index fde3e7a8b2e..16175a864b6 100644 --- a/packages/interactive_media_ads/example/pubspec.yaml +++ b/packages/interactive_media_ads/example/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: sdk: flutter interactive_media_ads: path: ../ + video_player: ^2.8.6 dev_dependencies: espresso: ^0.2.0 diff --git a/packages/interactive_media_ads/ios/interactive_media_ads.podspec b/packages/interactive_media_ads/ios/interactive_media_ads.podspec index 8980d486c21..bb42cc28763 100644 --- a/packages/interactive_media_ads/ios/interactive_media_ads.podspec +++ b/packages/interactive_media_ads/ios/interactive_media_ads.podspec @@ -14,7 +14,7 @@ Downloaded by pub (not CocoaPods). s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/interactive_media_ads/interactive_media_ads' } - s.source_files = 'Classes/**/*' + s.source_files = 'interactive_media_ads/Sources/interactive_media_ads/**/*.swift' s.dependency 'Flutter' s.platform = :ios, '12.0' @@ -25,5 +25,5 @@ Downloaded by pub (not CocoaPods). 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } s.swift_version = '5.0' - s.resource_bundles = {'interactive_media_ads_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'interactive_media_ads_privacy' => ['interactive_media_ads/Sources/interactive_media_ads/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/interactive_media_ads/ios/interactive_media_ads/Package.swift b/packages/interactive_media_ads/ios/interactive_media_ads/Package.swift new file mode 100644 index 00000000000..1864dffbb78 --- /dev/null +++ b/packages/interactive_media_ads/ios/interactive_media_ads/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "interactive_media_ads", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "interactive-media-ads", targets: ["interactive_media_ads"]) + ], + dependencies: [], + targets: [ + .target( + name: "interactive_media_ads", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/interactive_media_ads/ios/Classes/InteractiveMediaAdsPlugin.swift b/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/InteractiveMediaAdsPlugin.swift similarity index 100% rename from packages/interactive_media_ads/ios/Classes/InteractiveMediaAdsPlugin.swift rename to packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/InteractiveMediaAdsPlugin.swift diff --git a/packages/interactive_media_ads/ios/Resources/PrivacyInfo.xcprivacy b/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/interactive_media_ads/ios/Resources/PrivacyInfo.xcprivacy rename to packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/Resources/PrivacyInfo.xcprivacy diff --git a/packages/interactive_media_ads/lib/interactive_media_ads.dart b/packages/interactive_media_ads/lib/interactive_media_ads.dart index 185d304cf5b..b9e2aee98a3 100644 --- a/packages/interactive_media_ads/lib/interactive_media_ads.dart +++ b/packages/interactive_media_ads/lib/interactive_media_ads.dart @@ -5,6 +5,15 @@ export 'src/ad_display_container.dart'; export 'src/ads_loader.dart'; export 'src/ads_manager_delegate.dart'; -export 'src/platform_interface/ad_error.dart'; -export 'src/platform_interface/ad_event.dart'; -export 'src/platform_interface/ads_request.dart'; +export 'src/android/android_interactive_media_ads.dart' + show AndroidInteractiveMediaAds; +export 'src/platform_interface/platform_interface.dart' + show + AdError, + AdErrorCode, + AdErrorEvent, + AdErrorType, + AdEvent, + AdEventType, + AdsLoadErrorData, + AdsRequest; diff --git a/packages/interactive_media_ads/lib/src/ad_display_container.dart b/packages/interactive_media_ads/lib/src/ad_display_container.dart index 99da19682b8..cc254a33e51 100644 --- a/packages/interactive_media_ads/lib/src/ad_display_container.dart +++ b/packages/interactive_media_ads/lib/src/ad_display_container.dart @@ -7,7 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'platform_interface/platform_ad_display_container.dart'; import 'platform_interface/platform_interface.dart'; -/// Handles playing ads after they've been received from the server. +/// A [Widget] for displaying loaded ads. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current diff --git a/packages/interactive_media_ads/lib/src/ads_loader.dart b/packages/interactive_media_ads/lib/src/ads_loader.dart index 9370cc9fec3..4ab3a97c147 100644 --- a/packages/interactive_media_ads/lib/src/ads_loader.dart +++ b/packages/interactive_media_ads/lib/src/ads_loader.dart @@ -8,7 +8,8 @@ import 'ad_display_container.dart'; import 'ads_manager_delegate.dart'; import 'platform_interface/platform_interface.dart'; -/// Handles playing ads after they've been received from the server. +/// Allows publishers to request ads from ad servers or a dynamic ad insertion +/// stream. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current @@ -92,6 +93,9 @@ class AdsLoader { } /// Requests ads from a server. + /// + /// Ads cannot be requested until the `AdDisplayContainer` has been added to + /// the native View hierarchy. See [AdDisplayContainer.onContainerAdded]. Future requestAds(AdsRequest request) { return platform.requestAds(request); } diff --git a/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart index 4a3813c719a..c04cf5c9c52 100644 --- a/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart +++ b/packages/interactive_media_ads/lib/src/ads_manager_delegate.dart @@ -4,7 +4,8 @@ import 'platform_interface/platform_interface.dart'; -/// Handles playing ads after they've been received from the server. +/// Delegate for ad events or errors that occur during ad or stream +/// initialization and playback. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current diff --git a/packages/interactive_media_ads/lib/src/android/android_ad_display_container.dart b/packages/interactive_media_ads/lib/src/android/android_ad_display_container.dart new file mode 100644 index 00000000000..9822a78f7c3 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/android_ad_display_container.dart @@ -0,0 +1,279 @@ +// 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:async'; + +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; + +import '../platform_interface/platform_interface.dart'; +import 'android_view_widget.dart'; +import 'interactive_media_ads.g.dart' as ima; +import 'interactive_media_ads_proxy.dart'; +import 'platform_views_service_proxy.dart'; + +/// Android implementation of [PlatformAdDisplayContainerCreationParams]. +final class AndroidAdDisplayContainerCreationParams + extends PlatformAdDisplayContainerCreationParams { + /// Constructs a [AndroidAdDisplayContainerCreationParams]. + const AndroidAdDisplayContainerCreationParams({ + super.key, + required super.onContainerAdded, + @visibleForTesting InteractiveMediaAdsProxy? imaProxy, + @visibleForTesting PlatformViewsServiceProxy? platformViewsProxy, + }) : _imaProxy = imaProxy ?? const InteractiveMediaAdsProxy(), + _platformViewsProxy = + platformViewsProxy ?? const PlatformViewsServiceProxy(), + super(); + + /// Creates a [AndroidAdDisplayContainerCreationParams] from an instance of + /// [PlatformAdDisplayContainerCreationParams]. + factory AndroidAdDisplayContainerCreationParams.fromPlatformAdDisplayContainerCreationParams( + PlatformAdDisplayContainerCreationParams params, { + @visibleForTesting InteractiveMediaAdsProxy? imaProxy, + @visibleForTesting PlatformViewsServiceProxy? platformViewsProxy, + }) { + return AndroidAdDisplayContainerCreationParams( + key: params.key, + onContainerAdded: params.onContainerAdded, + imaProxy: imaProxy, + platformViewsProxy: platformViewsProxy, + ); + } + + final InteractiveMediaAdsProxy _imaProxy; + final PlatformViewsServiceProxy _platformViewsProxy; +} + +/// Android implementation of [PlatformAdDisplayContainer]. +/// +/// This acts as the video player for an ad. To be a player for an ad from the +/// IMA SDK: +/// 1. The [ima.VideoView] must be in the View hierarchy until all ads have +/// finished. +/// 2. Must respond to callbacks from the [ima.VideoAdPlayer]. +/// 3. Must trigger methods for [ima.VideoAdPlayerCallback]s that provide ad +/// playback information to the IMA SDK. [ima.VideoAdPlayerCallback]s are +/// provided by [ima.VideoAdPlayer.addCallback]. +/// 4. Must create an [ima.AdDisplayContainer] with the `ViewGroup` that +/// contains the `VideoView`. +base class AndroidAdDisplayContainer extends PlatformAdDisplayContainer { + /// Constructs an [AndroidAdDisplayContainer]. + AndroidAdDisplayContainer(super.params) : super.implementation() { + final WeakReference weakThis = + WeakReference(this); + _videoView = _setUpVideoView(weakThis); + _frameLayout.addView(_videoView); + _videoAdPlayer = _setUpVideoAdPlayer(weakThis); + } + + // The duration between each update to the IMA SDK of the progress of the + // currently playing ad. This value matches the one used in the Android + // example. + // See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side#6.-create-the-videoadplayeradapter-class + static const int _progressPollingMs = 250; + + // The `ViewGroup` used to create the native `ima.AdDisplayContainer`. The + // `View` that handles playing an ad is added as a child to this `ViewGroup`. + late final ima.FrameLayout _frameLayout = + _androidParams._imaProxy.newFrameLayout(); + + // Handles loading and displaying an ad. + late final ima.VideoView _videoView; + + // After an ad is loaded in the `VideoView`, this is used to control + // playback. + ima.MediaPlayer? _mediaPlayer; + + /// Methods that must be triggered to update the IMA SDK of the state of + /// playback of an ad. + @internal + final Set videoAdPlayerCallbacks = + {}; + + // Handles ad playback callbacks from the IMA SDK. For a player to be used for + // ad playback, the callbacks in this class must be implemented. This also + // provides `VideoAdPlayerCallback`s that contain methods that must be + // triggered by the player. + late final ima.VideoAdPlayer _videoAdPlayer; + + /// The native Android AdDisplayContainer. + /// + /// This holds the player for video ads. + /// + /// Created with the `ViewGroup` that contains the `View` that handles playing + /// an ad. + @internal + ima.AdDisplayContainer? adDisplayContainer; + + // Currently loaded ad. + ima.AdMediaInfo? _loadedAdMediaInfo; + + // The saved ad position, used to resume ad playback following an ad + // click-through. + int _savedAdPosition = 0; + + // Timer used to periodically update the IMA SDK of the progress of the + // currently playing ad. + Timer? _adProgressTimer; + + int? _adDuration; + + late final AndroidAdDisplayContainerCreationParams _androidParams = + params is AndroidAdDisplayContainerCreationParams + ? params as AndroidAdDisplayContainerCreationParams + : AndroidAdDisplayContainerCreationParams + .fromPlatformAdDisplayContainerCreationParams(params); + + @override + Widget build(BuildContext context) { + return AndroidViewWidget( + key: params.key, + view: _frameLayout, + platformViewsServiceProxy: _androidParams._platformViewsProxy, + onPlatformViewCreated: () async { + adDisplayContainer = await _androidParams._imaProxy + .createAdDisplayContainerImaSdkFactory( + _frameLayout, + _videoAdPlayer, + ); + params.onContainerAdded(this); + }, + ); + } + + // Clears the current `MediaPlayer` and resets any saved position of an ad. + // This should be used when current ad that is loaded in the `VideoView` is + // complete, failed to load/play, or has been stopped. + void _clearMediaPlayer() { + _mediaPlayer = null; + _savedAdPosition = 0; + } + + // Starts periodically updating the IMA SDK the progress of the currently + // playing ad. + // + // Setting a timer to periodically update the IMA SDK is also done in the + // official Android example: https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side#8.-set-up-ad-tracking. + void _startAdProgressTracking() { + // Stop any previous ad tracking. + _stopAdProgressTracking(); + _adProgressTimer = Timer.periodic( + const Duration(milliseconds: _progressPollingMs), + (Timer timer) async { + final ima.VideoProgressUpdate currentProgress = + _androidParams._imaProxy.newVideoProgressUpdate( + currentTimeMs: await _videoView.getCurrentPosition(), + durationMs: _adDuration!, + ); + await Future.wait( + >[ + _videoAdPlayer.setAdProgress(currentProgress), + ...videoAdPlayerCallbacks.map( + (ima.VideoAdPlayerCallback callback) async { + await callback.onAdProgress( + _loadedAdMediaInfo!, + currentProgress, + ); + }, + ), + ], + ); + }, + ); + } + + // Stops updating the IMA SDK the progress of the currently playing ad. + void _stopAdProgressTracking() { + _adProgressTimer?.cancel(); + _adProgressTimer = null; + } + + // This value is created in a static method because the callback methods for + // any wrapped classes must not reference the encapsulating object. This is to + // prevent a circular reference that prevents garbage collection. + static ima.VideoView _setUpVideoView( + WeakReference weakThis, + ) { + return weakThis.target!._androidParams._imaProxy.newVideoView( + onCompletion: (_, __) { + final AndroidAdDisplayContainer? container = weakThis.target; + if (container != null) { + container._clearMediaPlayer(); + container._stopAdProgressTracking(); + for (final ima.VideoAdPlayerCallback callback + in container.videoAdPlayerCallbacks) { + callback.onEnded(container._loadedAdMediaInfo!); + } + } + }, + onPrepared: (_, ima.MediaPlayer player) async { + final AndroidAdDisplayContainer? container = weakThis.target; + if (container != null) { + container._adDuration = await player.getDuration(); + container._mediaPlayer = player; + if (container._savedAdPosition > 0) { + await player.seekTo(container._savedAdPosition); + } + } + + await player.start(); + container?._startAdProgressTracking(); + }, + onError: (_, __, ___, ____) { + final AndroidAdDisplayContainer? container = weakThis.target; + if (container != null) { + container._clearMediaPlayer(); + for (final ima.VideoAdPlayerCallback callback + in container.videoAdPlayerCallbacks) { + callback.onError(container._loadedAdMediaInfo!); + } + container._loadedAdMediaInfo = null; + container._adDuration = null; + } + }, + ); + } + + // This value is created in a static method because the callback methods for + // any wrapped classes must not reference the encapsulating object. This is to + // prevent a circular reference that prevents garbage collection. + static ima.VideoAdPlayer _setUpVideoAdPlayer( + WeakReference weakThis, + ) { + return weakThis.target!._androidParams._imaProxy.newVideoAdPlayer( + addCallback: (_, ima.VideoAdPlayerCallback callback) { + weakThis.target?.videoAdPlayerCallbacks.add(callback); + }, + removeCallback: (_, ima.VideoAdPlayerCallback callback) { + weakThis.target?.videoAdPlayerCallbacks.remove(callback); + }, + loadAd: (_, ima.AdMediaInfo adMediaInfo, __) { + weakThis.target?._loadedAdMediaInfo = adMediaInfo; + }, + pauseAd: (_, __) async { + final AndroidAdDisplayContainer? container = weakThis.target; + if (container != null) { + await container._mediaPlayer!.pause(); + container._savedAdPosition = + await container._videoView.getCurrentPosition(); + container._stopAdProgressTracking(); + } + }, + playAd: (_, ima.AdMediaInfo adMediaInfo) { + weakThis.target?._videoView.setVideoUri(adMediaInfo.url); + }, + release: (_) {}, + stopAd: (_, __) { + final AndroidAdDisplayContainer? container = weakThis.target; + if (container != null) { + container._stopAdProgressTracking(); + container._clearMediaPlayer(); + container._loadedAdMediaInfo = null; + container._adDuration = null; + } + }, + ); + } +} diff --git a/packages/interactive_media_ads/lib/src/android/android_ads_loader.dart b/packages/interactive_media_ads/lib/src/android/android_ads_loader.dart new file mode 100644 index 00000000000..03fde8d22f2 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/android_ads_loader.dart @@ -0,0 +1,143 @@ +// 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:async'; + +import 'package:flutter/widgets.dart'; + +import '../platform_interface/platform_interface.dart'; +import 'android_ad_display_container.dart'; +import 'android_ads_manager.dart'; +import 'enum_converter_extensions.dart'; +import 'interactive_media_ads.g.dart' as ima; +import 'interactive_media_ads_proxy.dart'; + +/// Android implementation of [PlatformAdsLoaderCreationParams]. +final class AndroidAdsLoaderCreationParams + extends PlatformAdsLoaderCreationParams { + /// Constructs a [AndroidAdsLoaderCreationParams]. + const AndroidAdsLoaderCreationParams({ + required super.container, + required super.onAdsLoaded, + required super.onAdsLoadError, + @visibleForTesting InteractiveMediaAdsProxy? proxy, + }) : _proxy = proxy ?? const InteractiveMediaAdsProxy(), + super(); + + /// Creates a [AndroidAdsLoaderCreationParams] from an instance of + /// [PlatformAdsLoaderCreationParams]. + factory AndroidAdsLoaderCreationParams.fromPlatformAdsLoaderCreationParams( + PlatformAdsLoaderCreationParams params, { + @visibleForTesting InteractiveMediaAdsProxy? proxy, + }) { + return AndroidAdsLoaderCreationParams( + container: params.container, + onAdsLoaded: params.onAdsLoaded, + onAdsLoadError: params.onAdsLoadError, + proxy: proxy, + ); + } + + final InteractiveMediaAdsProxy _proxy; +} + +/// Android implementation of [PlatformAdsLoader]. +base class AndroidAdsLoader extends PlatformAdsLoader { + /// Constructs an [AndroidAdsLoader]. + AndroidAdsLoader(super.params) + : assert(params.container is AndroidAdDisplayContainer), + assert( + (params.container as AndroidAdDisplayContainer).adDisplayContainer != + null, + 'Ensure the AdDisplayContainer has been added to the Widget tree before creating an AdsLoader.', + ), + super.implementation() { + _adsLoaderFuture = _createAdsLoader(); + } + + late final ima.ImaSdkFactory _sdkFactory = + _androidParams._proxy.instanceImaSdkFactory(); + late Future _adsLoaderFuture; + + late final AndroidAdsLoaderCreationParams _androidParams = + params is AndroidAdsLoaderCreationParams + ? params as AndroidAdsLoaderCreationParams + : AndroidAdsLoaderCreationParams.fromPlatformAdsLoaderCreationParams( + params, + ); + + @override + Future contentComplete() async { + final Set callbacks = + (params.container as AndroidAdDisplayContainer).videoAdPlayerCallbacks; + await Future.wait( + callbacks.map( + (ima.VideoAdPlayerCallback callback) => callback.onContentComplete(), + ), + ); + } + + @override + Future requestAds(AdsRequest request) async { + final ima.AdsLoader adsLoader = await _adsLoaderFuture; + + final ima.AdsRequest androidRequest = await _sdkFactory.createAdsRequest(); + + await Future.wait(>[ + androidRequest.setAdTagUrl(request.adTagUrl), + adsLoader.requestAds(androidRequest), + ]); + } + + Future _createAdsLoader() async { + final ima.ImaSdkSettings settings = + await _sdkFactory.createImaSdkSettings(); + + final ima.AdsLoader adsLoader = await _sdkFactory.createAdsLoader( + settings, + (params.container as AndroidAdDisplayContainer).adDisplayContainer!, + ); + + _addListeners(WeakReference(this), adsLoader); + + return adsLoader; + } + + // This value is created in a static method because the callback methods for + // any wrapped classes must not reference the encapsulating object. This is to + // prevent a circular reference that prevents garbage collection. + static void _addListeners( + WeakReference weakThis, + ima.AdsLoader adsLoader, + ) { + final InteractiveMediaAdsProxy proxy = + weakThis.target!._androidParams._proxy; + adsLoader + ..addAdsLoadedListener(proxy.newAdsLoadedListener( + onAdsManagerLoaded: (_, ima.AdsManagerLoadedEvent event) { + weakThis.target?.params.onAdsLoaded( + PlatformOnAdsLoadedData( + manager: AndroidAdsManager( + event.manager, + proxy: weakThis.target?._androidParams._proxy, + ), + ), + ); + }, + )) + ..addAdErrorListener(proxy.newAdErrorListener( + onAdError: (_, ima.AdErrorEvent event) { + weakThis.target?.params.onAdsLoadError( + AdsLoadErrorData( + error: AdError( + type: event.error.errorType.asInterfaceErrorType(), + code: event.error.errorCode.asInterfaceErrorCode(), + message: event.error.message, + ), + ), + ); + }, + )); + } +} diff --git a/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart b/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart new file mode 100644 index 00000000000..7bf158ff52a --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/android_ads_manager.dart @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:meta/meta.dart'; + +import '../platform_interface/platform_interface.dart'; +import 'enum_converter_extensions.dart'; +import 'interactive_media_ads.g.dart' as ima; +import 'interactive_media_ads_proxy.dart'; + +/// Android implementation of [PlatformAdsManager]. +class AndroidAdsManager extends PlatformAdsManager { + /// Constructs an [AndroidAdsManager]. + @internal + AndroidAdsManager( + ima.AdsManager manager, { + InteractiveMediaAdsProxy? proxy, + }) : _manager = manager, + _proxy = proxy ?? const InteractiveMediaAdsProxy(); + + final ima.AdsManager _manager; + final InteractiveMediaAdsProxy _proxy; + + PlatformAdsManagerDelegate? _managerDelegate; + + @override + Future destroy() { + return _manager.destroy(); + } + + @override + Future init(AdsManagerInitParams params) { + return _manager.init(); + } + + @override + Future setAdsManagerDelegate( + PlatformAdsManagerDelegate delegate, + ) async { + _managerDelegate = delegate; + _addListeners(WeakReference(this)); + } + + @override + Future start(AdsManagerStartParams params) { + return _manager.start(); + } + + // This value is created in a static method because the callback methods for + // any wrapped classes must not reference the encapsulating object. This is to + // prevent a circular reference that prevents garbage collection. + static void _addListeners(WeakReference weakThis) { + final InteractiveMediaAdsProxy proxy = weakThis.target!._proxy; + weakThis.target?._manager.addAdEventListener( + proxy.newAdEventListener( + onAdEvent: (_, ima.AdEvent event) { + late final AdEventType? eventType = + event.type.asInterfaceAdEventType(); + if (eventType == null) { + return; + } + + weakThis.target?._managerDelegate?.params.onAdEvent + ?.call(AdEvent(type: eventType)); + }, + ), + ); + weakThis.target?._manager.addAdErrorListener( + proxy.newAdErrorListener( + onAdError: (_, ima.AdErrorEvent event) { + weakThis.target?._managerDelegate?.params.onAdErrorEvent?.call( + AdErrorEvent( + error: AdError( + type: event.error.errorType.asInterfaceErrorType(), + code: event.error.errorCode.asInterfaceErrorCode(), + message: event.error.message, + ), + ), + ); + weakThis.target?._manager.discardAdBreak(); + }, + ), + ); + } +} diff --git a/packages/interactive_media_ads/lib/src/android/android_ads_manager_delegate.dart b/packages/interactive_media_ads/lib/src/android/android_ads_manager_delegate.dart new file mode 100644 index 00000000000..24ab7eca926 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/android_ads_manager_delegate.dart @@ -0,0 +1,11 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../platform_interface/platform_interface.dart'; + +/// Android implementation of [PlatformAdsManagerDelegate]. +final class AndroidAdsManagerDelegate extends PlatformAdsManagerDelegate { + /// Constructs an [AndroidAdsManagerDelegate]. + AndroidAdsManagerDelegate(super.params) : super.implementation(); +} diff --git a/packages/interactive_media_ads/lib/src/android/android_interactive_media_ads.dart b/packages/interactive_media_ads/lib/src/android/android_interactive_media_ads.dart new file mode 100644 index 00000000000..2406520d553 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/android_interactive_media_ads.dart @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../platform_interface/interactive_media_ads_platform.dart'; +import '../platform_interface/platform_ad_display_container.dart'; +import '../platform_interface/platform_ads_loader.dart'; +import '../platform_interface/platform_ads_manager_delegate.dart'; +import 'android_ad_display_container.dart'; +import 'android_ads_loader.dart'; +import 'android_ads_manager_delegate.dart'; + +/// Android implementation of [InteractiveMediaAdsPlatform]. +final class AndroidInteractiveMediaAds extends InteractiveMediaAdsPlatform { + /// Registers this class as the default instance of [InteractiveMediaAdsPlatform]. + static void registerWith() { + InteractiveMediaAdsPlatform.instance = AndroidInteractiveMediaAds(); + } + + @override + PlatformAdDisplayContainer createPlatformAdDisplayContainer( + PlatformAdDisplayContainerCreationParams params, + ) { + return AndroidAdDisplayContainer(params); + } + + @override + PlatformAdsLoader createPlatformAdsLoader( + PlatformAdsLoaderCreationParams params, + ) { + return AndroidAdsLoader(params); + } + + @override + PlatformAdsManagerDelegate createPlatformAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams params, + ) { + return AndroidAdsManagerDelegate(params); + } +} diff --git a/packages/interactive_media_ads/lib/src/android/android_view_widget.dart b/packages/interactive_media_ads/lib/src/android/android_view_widget.dart new file mode 100644 index 00000000000..a4c0b662e98 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/android_view_widget.dart @@ -0,0 +1,103 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'interactive_media_ads.g.dart' as ima; +import 'platform_views_service_proxy.dart'; + +/// Represents a Flutter implementation of the Android [View](https://developer.android.com/reference/android/view/View) +/// that is created by the Android platform. +class AndroidViewWidget extends StatelessWidget { + /// Creates a [AndroidViewWidget]. + /// + /// The [AndroidViewWidget] should only be instantiated internally. + /// This constructor is visible for testing purposes only and should + /// never be called externally. + AndroidViewWidget({ + super.key, + required this.view, + this.layoutDirection = TextDirection.ltr, + this.onPlatformViewCreated, + this.displayWithHybridComposition = false, + ima.PigeonInstanceManager? instanceManager, + this.platformViewsServiceProxy = const PlatformViewsServiceProxy(), + }) : instanceManager = instanceManager ?? ima.PigeonInstanceManager.instance; + + /// The reference to the Android native view that should be shown. + final ima.View view; + + /// Maintains instances used to communicate with the native objects they + /// represent. + /// + /// This field is exposed for testing purposes only and should not be used + /// outside of tests. + final ima.PigeonInstanceManager instanceManager; + + /// Proxy that provides access to the platform views service. + /// + /// This service allows creating and controlling platform-specific views. + final PlatformViewsServiceProxy platformViewsServiceProxy; + + /// Whether to use Hybrid Composition to display the Android View. + final bool displayWithHybridComposition; + + /// Layout direction used by the Android View. + final TextDirection layoutDirection; + + /// Callback that will get invoke after the platform view has been created. + final VoidCallback? onPlatformViewCreated; + + @override + Widget build(BuildContext context) { + return PlatformViewLink( + viewType: 'plugins.flutter.io/webview', + surfaceFactory: ( + BuildContext context, + PlatformViewController controller, + ) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + gestureRecognizers: const >{}, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + return _initAndroidView(params) + ..addOnPlatformViewCreatedListener((int id) { + params.onPlatformViewCreated(id); + onPlatformViewCreated?.call(); + }) + ..create(); + }, + ); + } + + AndroidViewController _initAndroidView(PlatformViewCreationParams params) { + const String viewType = 'interactive_media_ads.packages.flutter.dev/view'; + final int? identifier = instanceManager.getIdentifier(view); + + if (displayWithHybridComposition) { + return platformViewsServiceProxy.initExpensiveAndroidView( + id: params.id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: identifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } else { + return platformViewsServiceProxy.initSurfaceAndroidView( + id: params.id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: identifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } + } +} diff --git a/packages/interactive_media_ads/lib/src/android/enum_converter_extensions.dart b/packages/interactive_media_ads/lib/src/android/enum_converter_extensions.dart new file mode 100644 index 00000000000..4d7293df429 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/enum_converter_extensions.dart @@ -0,0 +1,85 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../platform_interface/platform_interface.dart'; +import 'interactive_media_ads.g.dart' as ima; + +/// Adds a method to convert native AdErrorType to platform interface +/// AdErrorType. +extension NativeAdErrorTypeConverter on ima.AdErrorType { + /// Converts [ima.AdErrorType] to [AdErrorType]. + AdErrorType asInterfaceErrorType() { + return switch (this) { + ima.AdErrorType.load => AdErrorType.loading, + ima.AdErrorType.play => AdErrorType.playing, + ima.AdErrorType.unknown => AdErrorType.unknown, + }; + } +} + +/// Adds a method to convert native AdEventType to platform interface +/// AdEventType. +extension NativeAdEventTypeConverter on ima.AdEventType { + /// Attempts to convert an [ima.AdEventType] to [AdEventType]. + /// + /// Returns null is the type is not supported by the platform interface. + AdEventType? asInterfaceAdEventType() { + return switch (this) { + ima.AdEventType.allAdsCompleted => AdEventType.allAdsCompleted, + ima.AdEventType.completed => AdEventType.complete, + ima.AdEventType.contentPauseRequested => + AdEventType.contentPauseRequested, + ima.AdEventType.contentResumeRequested => + AdEventType.contentResumeRequested, + ima.AdEventType.loaded => AdEventType.loaded, + ima.AdEventType.clicked => AdEventType.clicked, + _ => null, + }; + } +} + +/// Adds a method to convert native AdErrorCode to platform interface +/// AdErrorCode. +extension NativeAdErrorCodeConverter on ima.AdErrorCode { + /// Converts [ima.AdErrorCode] to [AdErrorCode]. + AdErrorCode asInterfaceErrorCode() { + return switch (this) { + ima.AdErrorCode.adsPlayerWasNotProvided => + AdErrorCode.adsPlayerNotProvided, + ima.AdErrorCode.adsRequestNetworkError => + AdErrorCode.adsRequestNetworkError, + ima.AdErrorCode.companionAdLoadingFailed => + AdErrorCode.companionAdLoadingFailed, + ima.AdErrorCode.failedToRequestAds => AdErrorCode.failedToRequestAds, + ima.AdErrorCode.internalError => AdErrorCode.internalError, + ima.AdErrorCode.invalidArguments => AdErrorCode.invalidArguments, + ima.AdErrorCode.overlayAdLoadingFailed => + AdErrorCode.overlayAdLoadingFailed, + ima.AdErrorCode.overlayAdPlayingFailed => + AdErrorCode.overlayAdPlayingFailed, + ima.AdErrorCode.playlistNoContentTracking => + AdErrorCode.playlistNoContentTracking, + ima.AdErrorCode.unexpectedAdsLoadedEvent => + AdErrorCode.unexpectedAdsLoadedEvent, + ima.AdErrorCode.unknownAdResponse => AdErrorCode.unknownAdResponse, + ima.AdErrorCode.unknownError => AdErrorCode.unknownError, + ima.AdErrorCode.vastAssetNotFound => AdErrorCode.vastAssetNotFound, + ima.AdErrorCode.vastEmptyResponse => AdErrorCode.vastEmptyResponse, + ima.AdErrorCode.vastLinearAssetMismatch => + AdErrorCode.vastLinearAssetMismatch, + ima.AdErrorCode.vastLoadTimeout => AdErrorCode.vastLoadTimeout, + ima.AdErrorCode.vastMalformedResponse => + AdErrorCode.vastMalformedResponse, + ima.AdErrorCode.vastMediaLoadTimeout => AdErrorCode.vastMediaLoadTimeout, + ima.AdErrorCode.vastNonlinearAssetMismatch => + AdErrorCode.vastNonlinearAssetMismatch, + ima.AdErrorCode.vastNoAdsAfterWrapper => + AdErrorCode.vastNoAdsAfterWrapper, + ima.AdErrorCode.vastTooManyRedirects => AdErrorCode.vastTooManyRedirects, + ima.AdErrorCode.vastTraffickingError => AdErrorCode.vastTraffickingError, + ima.AdErrorCode.videoPlayError => AdErrorCode.videoPlayError, + ima.AdErrorCode.unknown => AdErrorCode.unknownError, + }; + } +} diff --git a/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart b/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart new file mode 100644 index 00000000000..5ba85481c3b --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/interactive_media_ads.g.dart @@ -0,0 +1,4726 @@ +// 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 (v19.1.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, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' + show ReadBuffer, WriteBuffer, immutable, protected; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart' show WidgetsFlutterBinding; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +/// An immutable object that serves as the base class for all ProxyApis and +/// can provide functional copies of itself. +/// +/// All implementers are expected to be [immutable] as defined by the annotation +/// and override [pigeon_copy] returning an instance of itself. +@immutable +abstract class PigeonProxyApiBaseClass { + /// Construct a [PigeonProxyApiBaseClass]. + PigeonProxyApiBaseClass({ + this.pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) : pigeon_instanceManager = + pigeon_instanceManager ?? PigeonInstanceManager.instance; + + /// Sends and receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used, which routes to + /// the host platform. + @protected + final BinaryMessenger? pigeon_binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + @protected + final PigeonInstanceManager pigeon_instanceManager; + + /// Instantiates and returns a functionally identical object to oneself. + /// + /// Outside of tests, this method should only ever be called by + /// [PigeonInstanceManager]. + /// + /// Subclasses should always override their parent's implementation of this + /// method. + @protected + PigeonProxyApiBaseClass pigeon_copy(); +} + +/// Maintains instances used to communicate with the native objects they +/// represent. +/// +/// Added instances are stored as weak references and their copies are stored +/// as strong references to maintain access to their variables and callback +/// methods. Both are stored with the same identifier. +/// +/// When a weak referenced instance becomes inaccessible, +/// [onWeakReferenceRemoved] is called with its associated identifier. +/// +/// If an instance is retrieved and has the possibility to be used, +/// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference +/// is added as a weak reference with the same identifier. This prevents a +/// scenario where the weak referenced instance was released and then later +/// returned by the host platform. +class PigeonInstanceManager { + /// Constructs a [PigeonInstanceManager]. + PigeonInstanceManager({required void Function(int) onWeakReferenceRemoved}) { + this.onWeakReferenceRemoved = (int identifier) { + _weakInstances.remove(identifier); + onWeakReferenceRemoved(identifier); + }; + _finalizer = Finalizer(this.onWeakReferenceRemoved); + } + + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously by the host platform. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + static const int _maxDartCreatedIdentifier = 65536; + + /// The default [PigeonInstanceManager] used by ProxyApis. + /// + /// On creation, this manager makes a call to clear the native + /// InstanceManager. This is to prevent identifier conflicts after a host + /// restart. + static final PigeonInstanceManager instance = _initInstance(); + + // Expando is used because it doesn't prevent its keys from becoming + // inaccessible. This allows the manager to efficiently retrieve an identifier + // of an instance without holding a strong reference to that instance. + // + // It also doesn't use `==` to search for identifiers, which would lead to an + // infinite loop when comparing an object to its copy. (i.e. which was caused + // by calling instanceManager.getIdentifier() inside of `==` while this was a + // HashMap). + final Expando _identifiers = Expando(); + final Map> _weakInstances = + >{}; + final Map _strongInstances = + {}; + late final Finalizer _finalizer; + int _nextIdentifier = 0; + + /// Called when a weak referenced instance is removed by [removeWeakReference] + /// or becomes inaccessible. + late final void Function(int) onWeakReferenceRemoved; + + static PigeonInstanceManager _initInstance() { + WidgetsFlutterBinding.ensureInitialized(); + final _PigeonInstanceManagerApi api = _PigeonInstanceManagerApi(); + // Clears the native `PigeonInstanceManager` on the initial use of the Dart one. + api.clear(); + final PigeonInstanceManager instanceManager = PigeonInstanceManager( + onWeakReferenceRemoved: (int identifier) { + api.removeStrongReference(identifier); + }, + ); + _PigeonInstanceManagerApi.setUpMessageHandlers( + instanceManager: instanceManager); + BaseDisplayContainer.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdDisplayContainer.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdsLoader.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdsManagerLoadedEvent.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdErrorEvent.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdError.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdsRequest.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + ContentProgressProvider.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdsManager.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + BaseManager.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdEvent.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + ImaSdkFactory.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + ImaSdkSettings.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + VideoProgressUpdate.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdMediaInfo.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdPodInfo.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + FrameLayout.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + ViewGroup.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + VideoView.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + View.pigeon_setUpMessageHandlers(pigeon_instanceManager: instanceManager); + MediaPlayer.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + VideoAdPlayerCallback.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + VideoAdPlayer.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdsLoadedListener.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdErrorListener.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + AdEventListener.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + return instanceManager; + } + + /// Adds a new instance that was instantiated by Dart. + /// + /// In other words, Dart wants to add a new instance that will represent + /// an object that will be instantiated on the host platform. + /// + /// Throws assertion error if the instance has already been added. + /// + /// Returns the randomly generated id of the [instance] added. + int addDartCreatedInstance(PigeonProxyApiBaseClass instance) { + final int identifier = _nextUniqueIdentifier(); + _addInstanceWithIdentifier(instance, identifier); + return identifier; + } + + /// Removes the instance, if present, and call [onWeakReferenceRemoved] with + /// its identifier. + /// + /// Returns the identifier associated with the removed instance. Otherwise, + /// `null` if the instance was not found in this manager. + /// + /// This does not remove the strong referenced instance associated with + /// [instance]. This can be done with [remove]. + int? removeWeakReference(PigeonProxyApiBaseClass instance) { + final int? identifier = getIdentifier(instance); + if (identifier == null) { + return null; + } + + _identifiers[instance] = null; + _finalizer.detach(instance); + onWeakReferenceRemoved(identifier); + + return identifier; + } + + /// Removes [identifier] and its associated strongly referenced instance, if + /// present, from the manager. + /// + /// Returns the strong referenced instance associated with [identifier] before + /// it was removed. Returns `null` if [identifier] was not associated with + /// any strong reference. + /// + /// This does not remove the weak referenced instance associated with + /// [identifier]. This can be done with [removeWeakReference]. + T? remove(int identifier) { + return _strongInstances.remove(identifier) as T?; + } + + /// Retrieves the instance associated with identifier. + /// + /// The value returned is chosen from the following order: + /// + /// 1. A weakly referenced instance associated with identifier. + /// 2. If the only instance associated with identifier is a strongly + /// referenced instance, a copy of the instance is added as a weak reference + /// with the same identifier. Returning the newly created copy. + /// 3. If no instance is associated with identifier, returns null. + /// + /// This method also expects the host `InstanceManager` to have a strong + /// reference to the instance the identifier is associated with. + T? getInstanceWithWeakReference( + int identifier) { + final PigeonProxyApiBaseClass? weakInstance = + _weakInstances[identifier]?.target; + + if (weakInstance == null) { + final PigeonProxyApiBaseClass? strongInstance = + _strongInstances[identifier]; + if (strongInstance != null) { + final PigeonProxyApiBaseClass copy = strongInstance.pigeon_copy(); + _identifiers[copy] = identifier; + _weakInstances[identifier] = + WeakReference(copy); + _finalizer.attach(copy, identifier, detach: copy); + return copy as T; + } + return strongInstance as T?; + } + + return weakInstance as T; + } + + /// Retrieves the identifier associated with instance. + int? getIdentifier(PigeonProxyApiBaseClass instance) { + return _identifiers[instance]; + } + + /// Adds a new instance that was instantiated by the host platform. + /// + /// In other words, the host platform wants to add a new instance that + /// represents an object on the host platform. Stored with [identifier]. + /// + /// Throws assertion error if the instance or its identifier has already been + /// added. + /// + /// Returns unique identifier of the [instance] added. + void addHostCreatedInstance( + PigeonProxyApiBaseClass instance, int identifier) { + _addInstanceWithIdentifier(instance, identifier); + } + + void _addInstanceWithIdentifier( + PigeonProxyApiBaseClass instance, int identifier) { + assert(!containsIdentifier(identifier)); + assert(getIdentifier(instance) == null); + assert(identifier >= 0); + + _identifiers[instance] = identifier; + _weakInstances[identifier] = + WeakReference(instance); + _finalizer.attach(instance, identifier, detach: instance); + + final PigeonProxyApiBaseClass copy = instance.pigeon_copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; + } + + /// Whether this manager contains the given [identifier]. + bool containsIdentifier(int identifier) { + return _weakInstances.containsKey(identifier) || + _strongInstances.containsKey(identifier); + } + + int _nextUniqueIdentifier() { + late int identifier; + do { + identifier = _nextIdentifier; + _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; + } while (containsIdentifier(identifier)); + return identifier; + } +} + +/// Generated API for managing the Dart and native `PigeonInstanceManager`s. +class _PigeonInstanceManagerApi { + /// Constructor for [_PigeonInstanceManagerApi]. + _PigeonInstanceManagerApi({BinaryMessenger? binaryMessenger}) + : __pigeon_binaryMessenger = binaryMessenger; + + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = + StandardMessageCodec(); + + static void setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? binaryMessenger, + PigeonInstanceManager? instanceManager, + }) { + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.removeStrongReference', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.removeStrongReference was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.removeStrongReference was null, expected non-null int.'); + try { + (instanceManager ?? PigeonInstanceManager.instance) + .remove(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + Future removeStrongReference(int identifier) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.removeStrongReference'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([identifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Clear the native `PigeonInstanceManager`. + /// + /// This is typically called after a hot restart. + Future clear() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.PigeonInstanceManagerApi.clear'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } +} + +class _PigeonProxyApiBaseCodec extends StandardMessageCodec { + const _PigeonProxyApiBaseCodec(this.instanceManager); + final PigeonInstanceManager instanceManager; + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is PigeonProxyApiBaseClass) { + buffer.putUint8(128); + writeValue(buffer, instanceManager.getIdentifier(value)); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return instanceManager + .getInstanceWithWeakReference(readValue(buffer)! as int); + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// The types of error that can be encountered. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.AdErrorCode.html. +enum AdErrorCode { + /// Ads player was not provided. + adsPlayerWasNotProvided, + + /// There was a problem requesting ads from the server. + adsRequestNetworkError, + + /// A companion ad failed to load or render. + companionAdLoadingFailed, + + /// There was a problem requesting ads from the server. + failedToRequestAds, + + /// An error internal to the SDK occurred. + internalError, + + /// Invalid arguments were provided to SDK methods. + invalidArguments, + + /// An overlay ad failed to load. + overlayAdLoadingFailed, + + /// An overlay ad failed to render. + overlayAdPlayingFailed, + + /// Ads list was returned but ContentProgressProvider was not configured. + playlistNoContentTracking, + + /// Ads loader sent ads loaded event when it was not expected. + unexpectedAdsLoadedEvent, + + /// The ad response was not understood and cannot be parsed. + unknownAdResponse, + + /// An unexpected error occurred and the cause is not known. + unknownError, + + /// No assets were found in the VAST ad response. + vastAssetNotFound, + + /// A VAST response containing a single `` tag with no child tags. + vastEmptyResponse, + + /// Assets were found in the VAST ad response for a linear ad, but none of + /// them matched the video player's capabilities. + vastLinearAssetMismatch, + + /// At least one VAST wrapper ad loaded successfully and a subsequent wrapper + /// or inline ad load has timed out. + vastLoadTimeout, + + /// The ad response was not recognized as a valid VAST ad. + vastMalformedResponse, + + /// Failed to load media assets from a VAST response. + vastMediaLoadTimeout, + + /// Assets were found in the VAST ad response for a nonlinear ad, but none of + /// them matched the video player's capabilities. + vastNonlinearAssetMismatch, + + /// No Ads VAST response after one or more wrappers. + vastNoAdsAfterWrapper, + + /// The maximum number of VAST wrapper redirects has been reached. + vastTooManyRedirects, + + /// Trafficking error. + /// + /// Video player received an ad type that it was not expecting and/or cannot + /// display. + vastTraffickingError, + + /// There was an error playing the video ad. + videoPlayError, + + /// The error code is not recognized by this wrapper. + unknown, +} + +/// Specifies when the error was encountered, during either ad loading or playback. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.AdErrorType.html. +enum AdErrorType { + /// Indicates that the error was encountered when the ad was being loaded. + load, + + /// Indicates that the error was encountered after the ad loaded, during ad play. + play, + + /// The error is not recognized by this wrapper. + unknown, +} + +/// Types of events that can occur during ad playback. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.AdEventType.html. +enum AdEventType { + /// Fired when an ad break in a stream ends. + adBreakEnded, + + /// Fired when an ad break will not play back any ads. + adBreakFetchError, + + /// Fired when an ad break is ready from VMAP or ad rule ads. + adBreakReady, + + /// Fired when an ad break in a stream starts. + adBreakStarted, + + /// Fired when playback stalls while the ad buffers. + adBuffering, + + /// Fired when an ad period in a stream ends. + adPeriodEnded, + + /// Fired when an ad period in a stream starts. + adPeriodStarted, + + /// Fired to inform of ad progress and can be used by publisher to display a + /// countdown timer. + adProgress, + + /// Fired when the ads manager is done playing all the valid ads in the ads + /// response, or when the response doesn't return any valid ads. + allAdsCompleted, + + /// Fired when an ad is clicked. + clicked, + + /// Fired when an ad completes playing. + completed, + + /// Fired when content should be paused. + contentPauseRequested, + + /// Fired when content should be resumed. + contentResumeRequested, + + /// Fired when VOD stream cuepoints have changed. + cuepointsChanged, + + /// Fired when the ad playhead crosses first quartile. + firstQuartile, + + /// The user has closed the icon fallback image dialog. + iconFallbackImageClosed, + + /// The user has tapped an ad icon. + iconTapped, + + /// Fired when the VAST response has been received. + loaded, + + /// Fired to enable the SDK to communicate a message to be logged, which is + /// stored in adData. + log, + + /// Fired when the ad playhead crosses midpoint. + midpoint, + + /// Fired when an ad is paused. + paused, + + /// Fired when an ad is resumed. + resumed, + + /// Fired when an ad changes its skippable state. + skippableStateChanged, + + /// Fired when an ad was skipped. + skipped, + + /// Fired when an ad starts playing. + started, + + /// Fired when a non-clickthrough portion of a video ad is clicked. + tapped, + + /// Fired when the ad playhead crosses third quartile. + thirdQuartile, + + /// The event type is not recognized by this wrapper. + unknown, +} + +/// A base class for more specialized container interfaces. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseDisplayContainer.html. +class BaseDisplayContainer extends PigeonProxyApiBaseClass { + /// Constructs [BaseDisplayContainer] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + BaseDisplayContainer.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + BaseDisplayContainer Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.BaseDisplayContainer.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.BaseDisplayContainer.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.BaseDisplayContainer.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + BaseDisplayContainer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + BaseDisplayContainer pigeon_copy() { + return BaseDisplayContainer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// A container in which to display the ads. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdDisplayContainer. +class AdDisplayContainer extends PigeonProxyApiBaseClass + implements BaseDisplayContainer { + /// Constructs [AdDisplayContainer] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdDisplayContainer.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdDisplayContainer Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdDisplayContainer.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdDisplayContainer.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdDisplayContainer.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + AdDisplayContainer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdDisplayContainer pigeon_copy() { + return AdDisplayContainer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// An object which allows publishers to request ads from ad servers or a +/// dynamic ad insertion stream. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsLoader. +class AdsLoader extends PigeonProxyApiBaseClass { + /// Constructs [AdsLoader] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdsLoader.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecAdsLoader = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdsLoader Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdsLoader.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsLoader.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsLoader.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + AdsLoader.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Registers a listener for errors that occur during the ads request. + Future addAdErrorListener(AdErrorListener listener) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecAdsLoader; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsLoader.addAdErrorListener'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, listener]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Registers a listener for the ads manager loaded event. + Future addAdsLoadedListener(AdsLoadedListener listener) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecAdsLoader; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsLoader.addAdsLoadedListener'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, listener]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Requests ads from a server. + Future requestAds(AdsRequest request) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecAdsLoader; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsLoader.requestAds'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, request]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + AdsLoader pigeon_copy() { + return AdsLoader.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// An event raised when ads are successfully loaded from the ad server through an AdsLoader. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsManagerLoadedEvent.html. +class AdsManagerLoadedEvent extends PigeonProxyApiBaseClass { + /// Constructs [AdsManagerLoadedEvent] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdsManagerLoadedEvent.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.manager, + }); + + /// The ads manager that will control playback of the loaded ads, or null when + /// using dynamic ad insertion. + final AdsManager manager; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdsManagerLoadedEvent Function(AdsManager manager)? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdsManagerLoadedEvent.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsManagerLoadedEvent.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsManagerLoadedEvent.pigeon_newInstance was null, expected non-null int.'); + final AdsManager? arg_manager = (args[1] as AdsManager?); + assert(arg_manager != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsManagerLoadedEvent.pigeon_newInstance was null, expected non-null AdsManager.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_manager!) ?? + AdsManagerLoadedEvent.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + manager: arg_manager!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdsManagerLoadedEvent pigeon_copy() { + return AdsManagerLoadedEvent.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + manager: manager, + ); + } +} + +/// An event raised when there is an error loading or playing ads. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdErrorEvent.html. +class AdErrorEvent extends PigeonProxyApiBaseClass { + /// Constructs [AdErrorEvent] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdErrorEvent.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.error, + }); + + /// The AdError that caused this event. + final AdError error; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdErrorEvent Function(AdError error)? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdErrorEvent.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdErrorEvent.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdErrorEvent.pigeon_newInstance was null, expected non-null int.'); + final AdError? arg_error = (args[1] as AdError?); + assert(arg_error != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdErrorEvent.pigeon_newInstance was null, expected non-null AdError.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_error!) ?? + AdErrorEvent.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + error: arg_error!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdErrorEvent pigeon_copy() { + return AdErrorEvent.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + error: error, + ); + } +} + +/// An error that occurred in the SDK. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.html. +class AdError extends PigeonProxyApiBaseClass { + /// Constructs [AdError] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdError.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.errorCode, + required this.errorCodeNumber, + required this.errorType, + required this.message, + }); + + /// The error's code. + final AdErrorCode errorCode; + + /// The error code's number. + final int errorCodeNumber; + + /// The error's type. + final AdErrorType errorType; + + /// A human-readable summary of the error. + final String message; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdError Function( + AdErrorCode errorCode, + int errorCodeNumber, + AdErrorType errorType, + String message, + )? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance was null, expected non-null int.'); + final AdErrorCode? arg_errorCode = + args[1] == null ? null : AdErrorCode.values[args[1]! as int]; + assert(arg_errorCode != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance was null, expected non-null AdErrorCode.'); + final int? arg_errorCodeNumber = (args[2] as int?); + assert(arg_errorCodeNumber != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance was null, expected non-null int.'); + final AdErrorType? arg_errorType = + args[3] == null ? null : AdErrorType.values[args[3]! as int]; + assert(arg_errorType != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance was null, expected non-null AdErrorType.'); + final String? arg_message = (args[4] as String?); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdError.pigeon_newInstance was null, expected non-null String.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_errorCode!, arg_errorCodeNumber!, + arg_errorType!, arg_message!) ?? + AdError.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + errorCode: arg_errorCode!, + errorCodeNumber: arg_errorCodeNumber!, + errorType: arg_errorType!, + message: arg_message!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdError pigeon_copy() { + return AdError.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + errorCode: errorCode, + errorCodeNumber: errorCodeNumber, + errorType: errorType, + message: message, + ); + } +} + +/// An object containing the data used to request ads from the server. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsRequest. +class AdsRequest extends PigeonProxyApiBaseClass { + /// Constructs [AdsRequest] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdsRequest.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecAdsRequest = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdsRequest Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdsRequest.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsRequest.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsRequest.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + AdsRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Sets the URL from which ads will be requested. + Future setAdTagUrl(String adTagUrl) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdsRequest; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsRequest.setAdTagUrl'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adTagUrl]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Attaches a ContentProgressProvider instance to allow scheduling ad breaks + /// based on content progress (cue points). + Future setContentProgressProvider( + ContentProgressProvider provider) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdsRequest; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsRequest.setContentProgressProvider'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, provider]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + AdsRequest pigeon_copy() { + return AdsRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Defines an interface to allow SDK to track progress of the content video. +/// +/// See https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/android/api/reference/com/google/ads/interactivemedia/v3/api/player/ContentProgressProvider.html. +class ContentProgressProvider extends PigeonProxyApiBaseClass { + /// Constructs [ContentProgressProvider] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ContentProgressProvider.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ContentProgressProvider Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.ContentProgressProvider.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ContentProgressProvider.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ContentProgressProvider.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ContentProgressProvider.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + ContentProgressProvider pigeon_copy() { + return ContentProgressProvider.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// An object which handles playing ads after they've been received from the +/// server. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsManager. +class AdsManager extends BaseManager { + /// Constructs [AdsManager] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdsManager.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }) : super.pigeon_detached(); + + late final _PigeonProxyApiBaseCodec __pigeon_codecAdsManager = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdsManager Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdsManager.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsManager.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsManager.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + AdsManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Discards current ad break and resumes content. + Future discardAdBreak() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdsManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsManager.discardAdBreak'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Pauses the current ad. + Future pause() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdsManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsManager.pause'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Starts playing the ads. + Future start() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdsManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsManager.start'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + AdsManager pigeon_copy() { + return AdsManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Base interface for managing ads.. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseManager.html. +class BaseManager extends PigeonProxyApiBaseClass { + /// Constructs [BaseManager] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + BaseManager.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecBaseManager = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + BaseManager Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.BaseManager.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.BaseManager.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.BaseManager.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + BaseManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Registers a listener for errors that occur during the ad or stream + /// initialization and playback. + Future addAdErrorListener(AdErrorListener errorListener) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecBaseManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.BaseManager.addAdErrorListener'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, errorListener]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Registers a listener for ad events that occur during ad or stream + /// initialization and playback. + Future addAdEventListener(AdEventListener adEventListener) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecBaseManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.BaseManager.addAdEventListener'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adEventListener]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Stops the ad and all tracking, then releases all assets that were loaded + /// to play the ad. + Future destroy() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecBaseManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.BaseManager.destroy'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Initializes the ad experience using default rendering settings + Future init() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecBaseManager; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.BaseManager.init'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + BaseManager pigeon_copy() { + return BaseManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Event to notify publisher that an event occurred with an Ad. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.html. +class AdEvent extends PigeonProxyApiBaseClass { + /// Constructs [AdEvent] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdEvent.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.type, + }); + + /// The type of event that occurred. + final AdEventType type; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdEvent Function(AdEventType type)? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdEvent.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdEvent.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdEvent.pigeon_newInstance was null, expected non-null int.'); + final AdEventType? arg_type = + args[1] == null ? null : AdEventType.values[args[1]! as int]; + assert(arg_type != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdEvent.pigeon_newInstance was null, expected non-null AdEventType.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_type!) ?? + AdEvent.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + type: arg_type!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdEvent pigeon_copy() { + return AdEvent.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + type: type, + ); + } +} + +/// Factory class for creating SDK objects. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/ImaSdkFactory. +class ImaSdkFactory extends PigeonProxyApiBaseClass { + /// Constructs [ImaSdkFactory] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ImaSdkFactory.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecImaSdkFactory = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static final ImaSdkFactory instance = __pigeon_instance(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ImaSdkFactory Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ImaSdkFactory.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + static ImaSdkFactory __pigeon_instance() { + final ImaSdkFactory __pigeon_instance = ImaSdkFactory.pigeon_detached(); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger __pigeon_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int __pigeon_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(__pigeon_instance); + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.instance'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + return __pigeon_instance; + } + + static Future createAdDisplayContainer( + ViewGroup container, + VideoAdPlayer player, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createAdDisplayContainer'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([container, player]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as AdDisplayContainer?)!; + } + } + + /// Creates an `ImaSdkSettings` object for configuring the IMA SDK. + Future createImaSdkSettings() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecImaSdkFactory; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createImaSdkSettings'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as ImaSdkSettings?)!; + } + } + + /// Creates an `AdsLoader` for requesting ads using the specified settings + /// object. + Future createAdsLoader( + ImaSdkSettings settings, + AdDisplayContainer container, + ) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecImaSdkFactory; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createAdsLoader'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, settings, container]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as AdsLoader?)!; + } + } + + /// Creates an AdsRequest object to contain the data used to request ads. + Future createAdsRequest() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecImaSdkFactory; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createAdsRequest'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as AdsRequest?)!; + } + } + + @override + ImaSdkFactory pigeon_copy() { + return ImaSdkFactory.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Defines general SDK settings that are used when creating an `AdsLoader`. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.html. +class ImaSdkSettings extends PigeonProxyApiBaseClass { + /// Constructs [ImaSdkSettings] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ImaSdkSettings.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ImaSdkSettings Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.ImaSdkSettings.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ImaSdkSettings.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ImaSdkSettings.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ImaSdkSettings.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + ImaSdkSettings pigeon_copy() { + return ImaSdkSettings.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Defines an update to the video's progress. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoProgressUpdate.html. +class VideoProgressUpdate extends PigeonProxyApiBaseClass { + VideoProgressUpdate({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required int currentTimeMs, + required int durationMs, + }) { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoProgressUpdate; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel.send( + [__pigeon_instanceIdentifier, currentTimeMs, durationMs]) + as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [VideoProgressUpdate] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + VideoProgressUpdate.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecVideoProgressUpdate = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + /// Value to use for cases when progress is not yet defined, such as video + /// initialization. + static final VideoProgressUpdate videoTimeNotReady = + __pigeon_videoTimeNotReady(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + VideoProgressUpdate Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + VideoProgressUpdate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + static VideoProgressUpdate __pigeon_videoTimeNotReady() { + final VideoProgressUpdate __pigeon_instance = + VideoProgressUpdate.pigeon_detached(); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger __pigeon_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int __pigeon_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(__pigeon_instance); + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoProgressUpdate.videoTimeNotReady'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + return __pigeon_instance; + } + + @override + VideoProgressUpdate pigeon_copy() { + return VideoProgressUpdate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// The minimal information required to play an ad. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/AdMediaInfo.html. +class AdMediaInfo extends PigeonProxyApiBaseClass { + /// Constructs [AdMediaInfo] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdMediaInfo.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.url, + }); + + final String url; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdMediaInfo Function(String url)? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdMediaInfo.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdMediaInfo.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdMediaInfo.pigeon_newInstance was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdMediaInfo.pigeon_newInstance was null, expected non-null String.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_url!) ?? + AdMediaInfo.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + url: arg_url!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdMediaInfo pigeon_copy() { + return AdMediaInfo.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + url: url, + ); + } +} + +/// An ad may be part of a pod of ads. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdPodInfo.html. +class AdPodInfo extends PigeonProxyApiBaseClass { + /// Constructs [AdPodInfo] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdPodInfo.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.adPosition, + required this.maxDuration, + required this.podIndex, + required this.timeOffset, + required this.totalAds, + required this.isBumper, + }); + + /// The position of the ad within the pod. + /// + /// The value returned is one-based, for example, 1 of 2, 2 of 2, etc. If the + /// ad is not part of a pod, this will return 1. + final int adPosition; + + /// The maximum duration of the pod in seconds. + /// + /// For unknown duration, -1 is returned. + final double maxDuration; + + /// Client side and DAI VOD: Returns the index of the ad pod. + final int podIndex; + + /// The content time offset at which the current ad pod was scheduled. + /// + /// For preroll pod, 0 is returned. For midrolls, the scheduled time is + /// returned in seconds. For postroll, -1 is returned. Defaults to 0 if this + /// ad is not part of a pod, or the pod is not part of an ad playlist. + final double timeOffset; + + /// The total number of ads contained within this pod, including bumpers. + final int totalAds; + + /// Returns true if the ad is a bumper ad. + final bool isBumper; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AdPodInfo Function( + int adPosition, + double maxDuration, + int podIndex, + double timeOffset, + int totalAds, + bool isBumper, + )? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null int.'); + final int? arg_adPosition = (args[1] as int?); + assert(arg_adPosition != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null int.'); + final double? arg_maxDuration = (args[2] as double?); + assert(arg_maxDuration != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null double.'); + final int? arg_podIndex = (args[3] as int?); + assert(arg_podIndex != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null int.'); + final double? arg_timeOffset = (args[4] as double?); + assert(arg_timeOffset != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null double.'); + final int? arg_totalAds = (args[5] as int?); + assert(arg_totalAds != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null int.'); + final bool? arg_isBumper = (args[6] as bool?); + assert(arg_isBumper != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdPodInfo.pigeon_newInstance was null, expected non-null bool.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call( + arg_adPosition!, + arg_maxDuration!, + arg_podIndex!, + arg_timeOffset!, + arg_totalAds!, + arg_isBumper!) ?? + AdPodInfo.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + adPosition: arg_adPosition!, + maxDuration: arg_maxDuration!, + podIndex: arg_podIndex!, + timeOffset: arg_timeOffset!, + totalAds: arg_totalAds!, + isBumper: arg_isBumper!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdPodInfo pigeon_copy() { + return AdPodInfo.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + adPosition: adPosition, + maxDuration: maxDuration, + podIndex: podIndex, + timeOffset: timeOffset, + totalAds: totalAds, + isBumper: isBumper, + ); + } +} + +/// FrameLayout is designed to block out an area on the screen to display a +/// single item. +/// +/// See https://developer.android.com/reference/android/widget/FrameLayout. +class FrameLayout extends ViewGroup { + FrameLayout({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }) : super.pigeon_detached() { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecFrameLayout; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.FrameLayout.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [FrameLayout] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + FrameLayout.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }) : super.pigeon_detached(); + + late final _PigeonProxyApiBaseCodec __pigeon_codecFrameLayout = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + FrameLayout Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.FrameLayout.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.FrameLayout.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.FrameLayout.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + FrameLayout.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + FrameLayout pigeon_copy() { + return FrameLayout.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// A special view that can contain other views (called children.) +/// +/// See https://developer.android.com/reference/android/view/ViewGroup. +class ViewGroup extends View { + /// Constructs [ViewGroup] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ViewGroup.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }) : super.pigeon_detached(); + + late final _PigeonProxyApiBaseCodec __pigeon_codecViewGroup = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ViewGroup Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.ViewGroup.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ViewGroup.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.ViewGroup.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ViewGroup.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + Future addView(View view) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecViewGroup; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.ViewGroup.addView'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, view]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + ViewGroup pigeon_copy() { + return ViewGroup.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Displays a video file. +/// +/// See https://developer.android.com/reference/android/widget/VideoView. +class VideoView extends View { + VideoView({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onPrepared, + this.onCompletion, + required this.onError, + }) : super.pigeon_detached() { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecVideoView; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoView.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [VideoView] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + VideoView.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onPrepared, + this.onCompletion, + required this.onError, + }) : super.pigeon_detached(); + + late final _PigeonProxyApiBaseCodec __pigeon_codecVideoView = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + /// Callback to be invoked when the media source is ready for playback. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoView instance = VideoView( + /// onPrepared: (VideoView pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoView pigeon_instance, + MediaPlayer player, + )? onPrepared; + + /// Callback to be invoked when playback of a media source has completed. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoView instance = VideoView( + /// onCompletion: (VideoView pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoView pigeon_instance, + MediaPlayer player, + )? onCompletion; + + /// Callback to be invoked when there has been an error during an asynchronous + /// operation. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoView instance = VideoView( + /// onError: (VideoView pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoView pigeon_instance, + MediaPlayer player, + int what, + int extra, + ) onError; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + VideoView pigeon_instance, + MediaPlayer player, + )? onPrepared, + void Function( + VideoView pigeon_instance, + MediaPlayer player, + )? onCompletion, + void Function( + VideoView pigeon_instance, + MediaPlayer player, + int what, + int extra, + )? onError, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoView.onPrepared', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onPrepared was null.'); + final List args = (message as List?)!; + final VideoView? arg_pigeon_instance = (args[0] as VideoView?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onPrepared was null, expected non-null VideoView.'); + final MediaPlayer? arg_player = (args[1] as MediaPlayer?); + assert(arg_player != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onPrepared was null, expected non-null MediaPlayer.'); + try { + (onPrepared ?? arg_pigeon_instance!.onPrepared) + ?.call(arg_pigeon_instance!, arg_player!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoView.onCompletion', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onCompletion was null.'); + final List args = (message as List?)!; + final VideoView? arg_pigeon_instance = (args[0] as VideoView?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onCompletion was null, expected non-null VideoView.'); + final MediaPlayer? arg_player = (args[1] as MediaPlayer?); + assert(arg_player != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onCompletion was null, expected non-null MediaPlayer.'); + try { + (onCompletion ?? arg_pigeon_instance!.onCompletion) + ?.call(arg_pigeon_instance!, arg_player!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoView.onError', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onError was null.'); + final List args = (message as List?)!; + final VideoView? arg_pigeon_instance = (args[0] as VideoView?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onError was null, expected non-null VideoView.'); + final MediaPlayer? arg_player = (args[1] as MediaPlayer?); + assert(arg_player != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onError was null, expected non-null MediaPlayer.'); + final int? arg_what = (args[2] as int?); + assert(arg_what != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onError was null, expected non-null int.'); + final int? arg_extra = (args[3] as int?); + assert(arg_extra != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoView.onError was null, expected non-null int.'); + try { + (onError ?? arg_pigeon_instance!.onError) + .call(arg_pigeon_instance!, arg_player!, arg_what!, arg_extra!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Sets the URI of the video. + Future setVideoUri(String uri) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecVideoView; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoView.setVideoUri'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, uri]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// The current position of the playing video. + /// + /// In milliseconds. + Future getCurrentPosition() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = __pigeon_codecVideoView; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoView.getCurrentPosition'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as int?)!; + } + } + + @override + VideoView pigeon_copy() { + return VideoView.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onPrepared: onPrepared, + onCompletion: onCompletion, + onError: onError, + ); + } +} + +/// This class represents the basic building block for user interface components. +/// +/// See https://developer.android.com/reference/android/view/View. +class View extends PigeonProxyApiBaseClass { + /// Constructs [View] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + View.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + View Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.View.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.View.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.View.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + View.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + View pigeon_copy() { + return View.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// MediaPlayer class can be used to control playback of audio/video files and +/// streams. +/// +/// See https://developer.android.com/reference/android/media/MediaPlayer. +class MediaPlayer extends PigeonProxyApiBaseClass { + /// Constructs [MediaPlayer] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + MediaPlayer.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecMediaPlayer = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + MediaPlayer Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.MediaPlayer.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.MediaPlayer.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.MediaPlayer.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + MediaPlayer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Gets the duration of the file. + Future getDuration() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecMediaPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.MediaPlayer.getDuration'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as int?)!; + } + } + + /// Seeks to specified time position. + Future seekTo(int mSec) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecMediaPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.MediaPlayer.seekTo'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, mSec]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Starts or resumes playback. + Future start() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecMediaPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.MediaPlayer.start'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Pauses playback. + Future pause() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecMediaPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.MediaPlayer.pause'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Stops playback after playback has been started or paused. + Future stop() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecMediaPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.MediaPlayer.stop'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + MediaPlayer pigeon_copy() { + return MediaPlayer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Callbacks that the player must fire. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoAdPlayer.VideoAdPlayerCallback.html +class VideoAdPlayerCallback extends PigeonProxyApiBaseClass { + /// Constructs [VideoAdPlayerCallback] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + VideoAdPlayerCallback.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecVideoAdPlayerCallback = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + VideoAdPlayerCallback Function()? pigeon_newInstance, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + VideoAdPlayerCallback.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// Fire this callback periodically as ad playback occurs. + Future onAdProgress( + AdMediaInfo adMediaInfo, + VideoProgressUpdate videoProgressUpdate, + ) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onAdProgress'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo, videoProgressUpdate]) + as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when video playback stalls waiting for data. + Future onBuffering(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onBuffering'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when all content has finished playing. + Future onContentComplete() async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onContentComplete'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the video finishes playing. + Future onEnded(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onEnded'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the video has encountered an error. + Future onError(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onError'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the video is ready to begin playback. + Future onLoaded(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onLoaded'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the video is paused. + Future onPause(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onPause'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the player begins playing a video. + Future onPlay(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onPlay'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the video is unpaused. + Future onResume(AdMediaInfo adMediaInfo) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onResume'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Fire this callback when the playback volume changes. + Future onVolumeChanged( + AdMediaInfo adMediaInfo, + int percentage, + ) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayerCallback; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayerCallback.onVolumeChanged'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, adMediaInfo, percentage]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + VideoAdPlayerCallback pigeon_copy() { + return VideoAdPlayerCallback.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Defines the set of methods that a video player must implement to be used by +/// the IMA SDK, as well as a set of callbacks that it must fire. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoAdPlayer.html. +class VideoAdPlayer extends PigeonProxyApiBaseClass { + VideoAdPlayer({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.addCallback, + required this.loadAd, + required this.pauseAd, + required this.playAd, + required this.release, + required this.removeCallback, + required this.stopAd, + }) { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [VideoAdPlayer] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + VideoAdPlayer.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.addCallback, + required this.loadAd, + required this.pauseAd, + required this.playAd, + required this.release, + required this.removeCallback, + required this.stopAd, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecVideoAdPlayer = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + /// Adds a callback. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// addCallback: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoAdPlayer pigeon_instance, + VideoAdPlayerCallback callback, + ) addCallback; + + /// Loads a video ad hosted at AdMediaInfo. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// loadAd: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + AdPodInfo adPodInfo, + ) loadAd; + + /// Pauses playing the current ad. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// pauseAd: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + ) pauseAd; + + /// Starts or resumes playing the video ad referenced by the AdMediaInfo, + /// provided loadAd has already been called for it. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// playAd: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + ) playAd; + + /// Cleans up and releases all resources used by the `VideoAdPlayer`. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// release: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(VideoAdPlayer pigeon_instance) release; + + /// Removes a callback. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// removeCallback: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoAdPlayer pigeon_instance, + VideoAdPlayerCallback callback, + ) removeCallback; + + /// Stops playing the current ad. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final VideoAdPlayer instance = VideoAdPlayer( + /// stopAd: (VideoAdPlayer pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + ) stopAd; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + VideoAdPlayer pigeon_instance, + VideoAdPlayerCallback callback, + )? addCallback, + void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + AdPodInfo adPodInfo, + )? loadAd, + void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + )? pauseAd, + void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + )? playAd, + void Function(VideoAdPlayer pigeon_instance)? release, + void Function( + VideoAdPlayer pigeon_instance, + VideoAdPlayerCallback callback, + )? removeCallback, + void Function( + VideoAdPlayer pigeon_instance, + AdMediaInfo adMediaInfo, + )? stopAd, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.addCallback', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.addCallback was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.addCallback was null, expected non-null VideoAdPlayer.'); + final VideoAdPlayerCallback? arg_callback = + (args[1] as VideoAdPlayerCallback?); + assert(arg_callback != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.addCallback was null, expected non-null VideoAdPlayerCallback.'); + try { + (addCallback ?? arg_pigeon_instance!.addCallback) + .call(arg_pigeon_instance!, arg_callback!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.loadAd', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.loadAd was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.loadAd was null, expected non-null VideoAdPlayer.'); + final AdMediaInfo? arg_adMediaInfo = (args[1] as AdMediaInfo?); + assert(arg_adMediaInfo != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.loadAd was null, expected non-null AdMediaInfo.'); + final AdPodInfo? arg_adPodInfo = (args[2] as AdPodInfo?); + assert(arg_adPodInfo != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.loadAd was null, expected non-null AdPodInfo.'); + try { + (loadAd ?? arg_pigeon_instance!.loadAd) + .call(arg_pigeon_instance!, arg_adMediaInfo!, arg_adPodInfo!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pauseAd', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pauseAd was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pauseAd was null, expected non-null VideoAdPlayer.'); + final AdMediaInfo? arg_adMediaInfo = (args[1] as AdMediaInfo?); + assert(arg_adMediaInfo != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.pauseAd was null, expected non-null AdMediaInfo.'); + try { + (pauseAd ?? arg_pigeon_instance!.pauseAd) + .call(arg_pigeon_instance!, arg_adMediaInfo!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.playAd', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.playAd was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.playAd was null, expected non-null VideoAdPlayer.'); + final AdMediaInfo? arg_adMediaInfo = (args[1] as AdMediaInfo?); + assert(arg_adMediaInfo != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.playAd was null, expected non-null AdMediaInfo.'); + try { + (playAd ?? arg_pigeon_instance!.playAd) + .call(arg_pigeon_instance!, arg_adMediaInfo!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.release', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.release was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.release was null, expected non-null VideoAdPlayer.'); + try { + (release ?? arg_pigeon_instance!.release) + .call(arg_pigeon_instance!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.removeCallback', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.removeCallback was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.removeCallback was null, expected non-null VideoAdPlayer.'); + final VideoAdPlayerCallback? arg_callback = + (args[1] as VideoAdPlayerCallback?); + assert(arg_callback != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.removeCallback was null, expected non-null VideoAdPlayerCallback.'); + try { + (removeCallback ?? arg_pigeon_instance!.removeCallback) + .call(arg_pigeon_instance!, arg_callback!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + + { + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.stopAd', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.stopAd was null.'); + final List args = (message as List?)!; + final VideoAdPlayer? arg_pigeon_instance = + (args[0] as VideoAdPlayer?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.stopAd was null, expected non-null VideoAdPlayer.'); + final AdMediaInfo? arg_adMediaInfo = (args[1] as AdMediaInfo?); + assert(arg_adMediaInfo != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.stopAd was null, expected non-null AdMediaInfo.'); + try { + (stopAd ?? arg_pigeon_instance!.stopAd) + .call(arg_pigeon_instance!, arg_adMediaInfo!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + /// The volume of the player as a percentage from 0 to 100. + Future setVolume(int value) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.setVolume'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([this, value]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// The `VideoProgressUpdate` describing playback progress of the current + /// video. + Future setAdProgress(VideoProgressUpdate progress) async { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecVideoAdPlayer; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.VideoAdPlayer.setAdProgress'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([this, progress]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + @override + VideoAdPlayer pigeon_copy() { + return VideoAdPlayer.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + addCallback: addCallback, + loadAd: loadAd, + pauseAd: pauseAd, + playAd: playAd, + release: release, + removeCallback: removeCallback, + stopAd: stopAd, + ); + } +} + +/// Listener interface for notification of ad load or stream load completion. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsLoader.AdsLoadedListener.html. +class AdsLoadedListener extends PigeonProxyApiBaseClass { + AdsLoadedListener({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onAdsManagerLoaded, + }) { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdsLoadedListener; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [AdsLoadedListener] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdsLoadedListener.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onAdsManagerLoaded, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecAdsLoadedListener = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + /// Called once the AdsManager or StreamManager has been loaded. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final AdsLoadedListener instance = AdsLoadedListener( + /// onAdsManagerLoaded: (AdsLoadedListener pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + AdsLoadedListener pigeon_instance, + AdsManagerLoadedEvent event, + ) onAdsManagerLoaded; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + AdsLoadedListener pigeon_instance, + AdsManagerLoadedEvent event, + )? onAdsManagerLoaded, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.onAdsManagerLoaded', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.onAdsManagerLoaded was null.'); + final List args = (message as List?)!; + final AdsLoadedListener? arg_pigeon_instance = + (args[0] as AdsLoadedListener?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.onAdsManagerLoaded was null, expected non-null AdsLoadedListener.'); + final AdsManagerLoadedEvent? arg_event = + (args[1] as AdsManagerLoadedEvent?); + assert(arg_event != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdsLoadedListener.onAdsManagerLoaded was null, expected non-null AdsManagerLoadedEvent.'); + try { + (onAdsManagerLoaded ?? arg_pigeon_instance!.onAdsManagerLoaded) + .call(arg_pigeon_instance!, arg_event!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdsLoadedListener pigeon_copy() { + return AdsLoadedListener.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onAdsManagerLoaded: onAdsManagerLoaded, + ); + } +} + +/// Interface for classes that will listen to AdErrorEvents. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdErrorEvent.AdErrorListener.html. +class AdErrorListener extends PigeonProxyApiBaseClass { + AdErrorListener({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onAdError, + }) { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdErrorListener; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdErrorListener.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [AdErrorListener] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdErrorListener.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onAdError, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecAdErrorListener = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + /// Called when an error occurs. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final AdErrorListener instance = AdErrorListener( + /// onAdError: (AdErrorListener pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + AdErrorListener pigeon_instance, + AdErrorEvent event, + ) onAdError; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + AdErrorListener pigeon_instance, + AdErrorEvent event, + )? onAdError, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdErrorListener.onAdError', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdErrorListener.onAdError was null.'); + final List args = (message as List?)!; + final AdErrorListener? arg_pigeon_instance = + (args[0] as AdErrorListener?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdErrorListener.onAdError was null, expected non-null AdErrorListener.'); + final AdErrorEvent? arg_event = (args[1] as AdErrorEvent?); + assert(arg_event != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdErrorListener.onAdError was null, expected non-null AdErrorEvent.'); + try { + (onAdError ?? arg_pigeon_instance!.onAdError) + .call(arg_pigeon_instance!, arg_event!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdErrorListener pigeon_copy() { + return AdErrorListener.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onAdError: onAdError, + ); + } +} + +/// Listener interface for ad events. +/// +/// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.AdEventListener.html. +class AdEventListener extends PigeonProxyApiBaseClass { + AdEventListener({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onAdEvent, + }) { + final int __pigeon_instanceIdentifier = + pigeon_instanceManager.addDartCreatedInstance(this); + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + __pigeon_codecAdEventListener; + final BinaryMessenger? __pigeon_binaryMessenger = pigeon_binaryMessenger; + () async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.interactive_media_ads.AdEventListener.pigeon_defaultConstructor'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([__pigeon_instanceIdentifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [AdEventListener] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AdEventListener.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onAdEvent, + }); + + late final _PigeonProxyApiBaseCodec __pigeon_codecAdEventListener = + _PigeonProxyApiBaseCodec(pigeon_instanceManager); + + /// Respond to an occurrence of an AdEvent. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final AdEventListener instance = AdEventListener( + /// onAdEvent: (AdEventListener pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + AdEventListener pigeon_instance, + AdEvent event, + ) onAdEvent; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + AdEventListener pigeon_instance, + AdEvent event, + )? onAdEvent, + }) { + final _PigeonProxyApiBaseCodec pigeonChannelCodec = + _PigeonProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.interactive_media_ads.AdEventListener.onAdEvent', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdEventListener.onAdEvent was null.'); + final List args = (message as List?)!; + final AdEventListener? arg_pigeon_instance = + (args[0] as AdEventListener?); + assert(arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdEventListener.onAdEvent was null, expected non-null AdEventListener.'); + final AdEvent? arg_event = (args[1] as AdEvent?); + assert(arg_event != null, + 'Argument for dev.flutter.pigeon.interactive_media_ads.AdEventListener.onAdEvent was null, expected non-null AdEvent.'); + try { + (onAdEvent ?? arg_pigeon_instance!.onAdEvent) + .call(arg_pigeon_instance!, arg_event!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + @override + AdEventListener pigeon_copy() { + return AdEventListener.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onAdEvent: onAdEvent, + ); + } +} diff --git a/packages/interactive_media_ads/lib/src/android/interactive_media_ads_proxy.dart b/packages/interactive_media_ads/lib/src/android/interactive_media_ads_proxy.dart new file mode 100644 index 00000000000..eff59354136 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/interactive_media_ads_proxy.dart @@ -0,0 +1,90 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'interactive_media_ads.g.dart'; + +/// Handles constructing objects and calling static methods for the Android +/// Interactive Media Ads native library. +/// +/// This class provides dependency injection for the implementations of the +/// platform interface classes. Improving the ease of unit testing and/or +/// overriding the underlying Android classes. +/// +/// By default each function calls the default constructor of the class it +/// intends to return. +class InteractiveMediaAdsProxy { + /// Constructs an [InteractiveMediaAdsProxy]. + const InteractiveMediaAdsProxy({ + this.newVideoProgressUpdate = VideoProgressUpdate.new, + this.newFrameLayout = FrameLayout.new, + this.newVideoView = VideoView.new, + this.newVideoAdPlayer = VideoAdPlayer.new, + this.newAdsLoadedListener = AdsLoadedListener.new, + this.newAdErrorListener = AdErrorListener.new, + this.newAdEventListener = AdEventListener.new, + this.createAdDisplayContainerImaSdkFactory = + ImaSdkFactory.createAdDisplayContainer, + this.instanceImaSdkFactory = _instanceImaSdkFactory, + this.videoTimeNotReadyVideoProgressUpdate = + _videoTimeNotReadyVideoProgressUpdate, + }); + + /// Constructs [VideoProgressUpdate]. + final VideoProgressUpdate Function({ + required int currentTimeMs, + required int durationMs, + }) newVideoProgressUpdate; + + /// Constructs [FrameLayout]. + final FrameLayout Function() newFrameLayout; + + /// Constructs [VideoView]. + final VideoView Function({ + required void Function(VideoView, MediaPlayer, int, int) onError, + void Function(VideoView, MediaPlayer)? onPrepared, + void Function(VideoView, MediaPlayer)? onCompletion, + }) newVideoView; + + /// Constructs [VideoAdPlayer]. + final VideoAdPlayer Function({ + required void Function(VideoAdPlayer, VideoAdPlayerCallback) addCallback, + required void Function(VideoAdPlayer, AdMediaInfo, AdPodInfo) loadAd, + required void Function(VideoAdPlayer, AdMediaInfo) pauseAd, + required void Function(VideoAdPlayer, AdMediaInfo) playAd, + required void Function(VideoAdPlayer) release, + required void Function(VideoAdPlayer, VideoAdPlayerCallback) removeCallback, + required void Function(VideoAdPlayer, AdMediaInfo) stopAd, + }) newVideoAdPlayer; + + /// Constructs [AdsLoadedListener]. + final AdsLoadedListener Function({ + required void Function(AdsLoadedListener, AdsManagerLoadedEvent) + onAdsManagerLoaded, + }) newAdsLoadedListener; + + /// Constructs [AdErrorListener]. + final AdErrorListener Function({ + required void Function(AdErrorListener, AdErrorEvent) onAdError, + }) newAdErrorListener; + + /// Constructs [AdEventListener]. + final AdEventListener Function({ + required void Function(AdEventListener, AdEvent) onAdEvent, + }) newAdEventListener; + + /// Calls to [ImaSdkFactory.createAdDisplayContainer]. + final Future Function(ViewGroup, VideoAdPlayer) + createAdDisplayContainerImaSdkFactory; + + /// Calls to [ImaSdkFactory.instance]. + final ImaSdkFactory Function() instanceImaSdkFactory; + + /// Calls to [VideoProgressUpdate.videoTimeNotReady]. + final VideoProgressUpdate Function() videoTimeNotReadyVideoProgressUpdate; + + static ImaSdkFactory _instanceImaSdkFactory() => ImaSdkFactory.instance; + + static VideoProgressUpdate _videoTimeNotReadyVideoProgressUpdate() => + VideoProgressUpdate.videoTimeNotReady; +} diff --git a/packages/interactive_media_ads/lib/src/android/platform_views_service_proxy.dart b/packages/interactive_media_ads/lib/src/android/platform_views_service_proxy.dart new file mode 100644 index 00000000000..6e0501cb809 --- /dev/null +++ b/packages/interactive_media_ads/lib/src/android/platform_views_service_proxy.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +/// Proxy that provides access to the platform views service. +/// +/// This service allows creating and controlling platform-specific views. +@immutable +class PlatformViewsServiceProxy { + /// Constructs a [PlatformViewsServiceProxy]. + const PlatformViewsServiceProxy(); + + /// Proxy method for [PlatformViewsService.initExpensiveAndroidView]. + ExpensiveAndroidViewController initExpensiveAndroidView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + return PlatformViewsService.initExpensiveAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + onFocus: onFocus, + ); + } + + /// Proxy method for [PlatformViewsService.initSurfaceAndroidView]. + SurfaceAndroidViewController initSurfaceAndroidView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + return PlatformViewsService.initSurfaceAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + onFocus: onFocus, + ); + } +} diff --git a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart index d80a17a6a73..85488aaa251 100644 --- a/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart +++ b/packages/interactive_media_ads/lib/src/platform_interface/platform_ads_manager.dart @@ -13,7 +13,7 @@ base class AdsManagerInitParams {} /// ads. base class AdsManagerStartParams {} -/// Interface for a platform implementation of a `AdsManager`. +/// Interface for a platform implementation of an `AdsManager`. abstract class PlatformAdsManager { /// Creates a [PlatformAdsManager]. @protected diff --git a/packages/interactive_media_ads/pigeons/copyright.txt b/packages/interactive_media_ads/pigeons/copyright.txt new file mode 100644 index 00000000000..fb682b1ab96 --- /dev/null +++ b/packages/interactive_media_ads/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. \ No newline at end of file diff --git a/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart b/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart new file mode 100644 index 00000000000..972a1e6818c --- /dev/null +++ b/packages/interactive_media_ads/pigeons/interactive_media_ads_android.dart @@ -0,0 +1,723 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(bparrishMines): Uncomment this file once +// https://github.com/flutter/packages/pull/6371 lands. This file uses the +// Kotlin ProxyApi feature from pigeon. +// ignore_for_file: avoid_unused_constructor_parameters +// +// import 'package:pigeon/pigeon.dart'; +// +// @ConfigurePigeon( +// PigeonOptions( +// copyrightHeader: 'pigeons/copyright.txt', +// dartOut: 'lib/src/android/interactive_media_ads.g.dart', +// kotlinOut: +// 'android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt', +// kotlinOptions: KotlinOptions( +// package: 'dev.flutter.packages.interactive_media_ads', +// ), +// ), +// ) +// +// /// The types of error that can be encountered. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.AdErrorCode.html. +// enum AdErrorCode { +// /// Ads player was not provided. +// adsPlayerWasNotProvided, +// +// /// There was a problem requesting ads from the server. +// adsRequestNetworkError, +// +// /// A companion ad failed to load or render. +// companionAdLoadingFailed, +// +// /// There was a problem requesting ads from the server. +// failedToRequestAds, +// +// /// An error internal to the SDK occurred. +// internalError, +// +// /// Invalid arguments were provided to SDK methods. +// invalidArguments, +// +// /// An overlay ad failed to load. +// overlayAdLoadingFailed, +// +// /// An overlay ad failed to render. +// overlayAdPlayingFailed, +// +// /// Ads list was returned but ContentProgressProvider was not configured. +// playlistNoContentTracking, +// +// /// Ads loader sent ads loaded event when it was not expected. +// unexpectedAdsLoadedEvent, +// +// /// The ad response was not understood and cannot be parsed. +// unknownAdResponse, +// +// /// An unexpected error occurred and the cause is not known. +// unknownError, +// +// /// No assets were found in the VAST ad response. +// vastAssetNotFound, +// +// /// A VAST response containing a single `` tag with no child tags. +// vastEmptyResponse, +// +// /// Assets were found in the VAST ad response for a linear ad, but none of +// /// them matched the video player's capabilities. +// vastLinearAssetMismatch, +// +// /// At least one VAST wrapper ad loaded successfully and a subsequent wrapper +// /// or inline ad load has timed out. +// vastLoadTimeout, +// +// /// The ad response was not recognized as a valid VAST ad. +// vastMalformedResponse, +// +// /// Failed to load media assets from a VAST response. +// vastMediaLoadTimeout, +// +// /// Assets were found in the VAST ad response for a nonlinear ad, but none of +// /// them matched the video player's capabilities. +// vastNonlinearAssetMismatch, +// +// /// No Ads VAST response after one or more wrappers. +// vastNoAdsAfterWrapper, +// +// /// The maximum number of VAST wrapper redirects has been reached. +// vastTooManyRedirects, +// +// /// Trafficking error. +// /// +// /// Video player received an ad type that it was not expecting and/or cannot +// /// display. +// vastTraffickingError, +// +// /// There was an error playing the video ad. +// videoPlayError, +// +// /// The error code is not recognized by this wrapper. +// unknown, +// } +// +// /// Specifies when the error was encountered, during either ad loading or playback. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.AdErrorType.html. +// enum AdErrorType { +// /// Indicates that the error was encountered when the ad was being loaded. +// load, +// +// /// Indicates that the error was encountered after the ad loaded, during ad play. +// play, +// +// /// The error is not recognized by this wrapper. +// unknown, +// } +// +// /// Types of events that can occur during ad playback. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.AdEventType.html. +// enum AdEventType { +// /// Fired when an ad break in a stream ends. +// adBreakEnded, +// +// /// Fired when an ad break will not play back any ads. +// adBreakFetchError, +// +// /// Fired when an ad break is ready from VMAP or ad rule ads. +// adBreakReady, +// +// /// Fired when an ad break in a stream starts. +// adBreakStarted, +// +// /// Fired when playback stalls while the ad buffers. +// adBuffering, +// +// /// Fired when an ad period in a stream ends. +// adPeriodEnded, +// +// /// Fired when an ad period in a stream starts. +// adPeriodStarted, +// +// /// Fired to inform of ad progress and can be used by publisher to display a +// /// countdown timer. +// adProgress, +// +// /// Fired when the ads manager is done playing all the valid ads in the ads +// /// response, or when the response doesn't return any valid ads. +// allAdsCompleted, +// +// /// Fired when an ad is clicked. +// clicked, +// +// /// Fired when an ad completes playing. +// completed, +// +// /// Fired when content should be paused. +// contentPauseRequested, +// +// /// Fired when content should be resumed. +// contentResumeRequested, +// +// /// Fired when VOD stream cuepoints have changed. +// cuepointsChanged, +// +// /// Fired when the ad playhead crosses first quartile. +// firstQuartile, +// +// /// The user has closed the icon fallback image dialog. +// iconFallbackImageClosed, +// +// /// The user has tapped an ad icon. +// iconTapped, +// +// /// Fired when the VAST response has been received. +// loaded, +// +// /// Fired to enable the SDK to communicate a message to be logged, which is +// /// stored in adData. +// log, +// +// /// Fired when the ad playhead crosses midpoint. +// midpoint, +// +// /// Fired when an ad is paused. +// paused, +// +// /// Fired when an ad is resumed. +// resumed, +// +// /// Fired when an ad changes its skippable state. +// skippableStateChanged, +// +// /// Fired when an ad was skipped. +// skipped, +// +// /// Fired when an ad starts playing. +// started, +// +// /// Fired when a non-clickthrough portion of a video ad is clicked. +// tapped, +// +// /// Fired when the ad playhead crosses third quartile. +// thirdQuartile, +// +// /// The event type is not recognized by this wrapper. +// unknown, +// } +// +// /// A base class for more specialized container interfaces. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseDisplayContainer.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.BaseDisplayContainer', +// ), +// ) +// abstract class BaseDisplayContainer {} +// +// /// A container in which to display the ads. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdDisplayContainer. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdDisplayContainer', +// ), +// ) +// abstract class AdDisplayContainer implements BaseDisplayContainer {} +// +// /// An object which allows publishers to request ads from ad servers or a +// /// dynamic ad insertion stream. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsLoader. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdsLoader', +// ), +// ) +// abstract class AdsLoader { +// /// Registers a listener for errors that occur during the ads request. +// void addAdErrorListener(AdErrorListener listener); +// +// /// Registers a listener for the ads manager loaded event. +// void addAdsLoadedListener(AdsLoadedListener listener); +// +// /// Requests ads from a server. +// void requestAds(AdsRequest request); +// } +// +// /// An event raised when ads are successfully loaded from the ad server through an AdsLoader. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsManagerLoadedEvent.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent', +// ), +// ) +// abstract class AdsManagerLoadedEvent { +// /// The ads manager that will control playback of the loaded ads, or null when +// /// using dynamic ad insertion. +// late final AdsManager manager; +// } +// +// /// An event raised when there is an error loading or playing ads. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdErrorEvent.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdErrorEvent', +// ), +// ) +// abstract class AdErrorEvent { +// /// The AdError that caused this event. +// late final AdError error; +// } +// +// /// An error that occurred in the SDK. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdError.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdError', +// ), +// ) +// abstract class AdError { +// /// The error's code. +// late final AdErrorCode errorCode; +// +// /// The error code's number. +// late final int errorCodeNumber; +// +// /// The error's type. +// late final AdErrorType errorType; +// +// /// A human-readable summary of the error. +// late final String message; +// } +// +// /// An object containing the data used to request ads from the server. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsRequest. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdsRequest', +// ), +// ) +// abstract class AdsRequest { +// /// Sets the URL from which ads will be requested. +// void setAdTagUrl(String adTagUrl); +// +// /// Attaches a ContentProgressProvider instance to allow scheduling ad breaks +// /// based on content progress (cue points). +// void setContentProgressProvider(ContentProgressProvider provider); +// } +// +// /// Defines an interface to allow SDK to track progress of the content video. +// /// +// /// See https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/android/api/reference/com/google/ads/interactivemedia/v3/api/player/ContentProgressProvider.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider', +// ), +// ) +// abstract class ContentProgressProvider {} +// +// /// An object which handles playing ads after they've been received from the +// /// server. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsManager. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdsManager', +// ), +// ) +// abstract class AdsManager extends BaseManager { +// /// Discards current ad break and resumes content. +// void discardAdBreak(); +// +// /// Pauses the current ad. +// void pause(); +// +// /// Starts playing the ads. +// void start(); +// } +// +// /// Base interface for managing ads.. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseManager.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.BaseManager', +// ), +// ) +// abstract class BaseManager { +// /// Registers a listener for errors that occur during the ad or stream +// /// initialization and playback. +// void addAdErrorListener(AdErrorListener errorListener); +// +// /// Registers a listener for ad events that occur during ad or stream +// /// initialization and playback. +// void addAdEventListener(AdEventListener adEventListener); +// +// /// Stops the ad and all tracking, then releases all assets that were loaded +// /// to play the ad. +// void destroy(); +// +// /// Initializes the ad experience using default rendering settings +// void init(); +// } +// +// /// Event to notify publisher that an event occurred with an Ad. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdEvent', +// ), +// ) +// abstract class AdEvent { +// /// The type of event that occurred. +// late final AdEventType type; +// } +// +// /// Factory class for creating SDK objects. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/ImaSdkFactory. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.ImaSdkFactory', +// ), +// ) +// abstract class ImaSdkFactory { +// @static +// @attached +// late final ImaSdkFactory instance; +// +// @static +// AdDisplayContainer createAdDisplayContainer( +// ViewGroup container, +// VideoAdPlayer player, +// ); +// +// /// Creates an `ImaSdkSettings` object for configuring the IMA SDK. +// ImaSdkSettings createImaSdkSettings(); +// +// /// Creates an `AdsLoader` for requesting ads using the specified settings +// /// object. +// AdsLoader createAdsLoader( +// ImaSdkSettings settings, +// AdDisplayContainer container, +// ); +// +// /// Creates an AdsRequest object to contain the data used to request ads. +// AdsRequest createAdsRequest(); +// } +// +// /// Defines general SDK settings that are used when creating an `AdsLoader`. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.ImaSdkSettings', +// ), +// ) +// abstract class ImaSdkSettings {} +// +// /// Defines an update to the video's progress. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoProgressUpdate.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate', +// ), +// ) +// abstract class VideoProgressUpdate { +// VideoProgressUpdate(int currentTimeMs, int durationMs); +// +// /// Value to use for cases when progress is not yet defined, such as video +// /// initialization. +// @static +// @attached +// late final VideoProgressUpdate videoTimeNotReady; +// } +// +// /// The minimal information required to play an ad. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/AdMediaInfo.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.player.AdMediaInfo', +// ), +// ) +// abstract class AdMediaInfo { +// late final String url; +// } +// +// /// An ad may be part of a pod of ads. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdPodInfo.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'com.google.ads.interactivemedia.v3.api.AdPodInfo', +// ), +// ) +// abstract class AdPodInfo { +// /// The position of the ad within the pod. +// /// +// /// The value returned is one-based, for example, 1 of 2, 2 of 2, etc. If the +// /// ad is not part of a pod, this will return 1. +// late final int adPosition; +// +// /// The maximum duration of the pod in seconds. +// /// +// /// For unknown duration, -1 is returned. +// late final double maxDuration; +// +// /// Client side and DAI VOD: Returns the index of the ad pod. +// late final int podIndex; +// +// /// The content time offset at which the current ad pod was scheduled. +// /// +// /// For preroll pod, 0 is returned. For midrolls, the scheduled time is +// /// returned in seconds. For postroll, -1 is returned. Defaults to 0 if this +// /// ad is not part of a pod, or the pod is not part of an ad playlist. +// late final double timeOffset; +// +// /// The total number of ads contained within this pod, including bumpers. +// late final int totalAds; +// +// /// Returns true if the ad is a bumper ad. +// late final bool isBumper; +// } +// +// /// FrameLayout is designed to block out an area on the screen to display a +// /// single item. +// /// +// /// See https://developer.android.com/reference/android/widget/FrameLayout. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'android.widget.FrameLayout', +// ), +// ) +// abstract class FrameLayout extends ViewGroup { +// FrameLayout(); +// } +// +// /// A special view that can contain other views (called children.) +// /// +// /// See https://developer.android.com/reference/android/view/ViewGroup. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'android.view.ViewGroup', +// ), +// ) +// abstract class ViewGroup extends View { +// void addView(View view); +// } +// +// /// Displays a video file. +// /// +// /// See https://developer.android.com/reference/android/widget/VideoView. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'android.widget.VideoView', +// ), +// ) +// abstract class VideoView extends View { +// VideoView(); +// +// /// Callback to be invoked when the media source is ready for playback. +// late final void Function(MediaPlayer player)? onPrepared; +// +// /// Callback to be invoked when playback of a media source has completed. +// late final void Function(MediaPlayer player)? onCompletion; +// +// /// Callback to be invoked when there has been an error during an asynchronous +// /// operation. +// late final void Function(MediaPlayer player, int what, int extra) onError; +// +// /// Sets the URI of the video. +// void setVideoUri(String uri); +// +// /// The current position of the playing video. +// /// +// /// In milliseconds. +// int getCurrentPosition(); +// } +// +// /// This class represents the basic building block for user interface components. +// /// +// /// See https://developer.android.com/reference/android/view/View. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions(fullClassName: 'android.view.View'), +// ) +// abstract class View {} +// +// /// MediaPlayer class can be used to control playback of audio/video files and +// /// streams. +// /// +// /// See https://developer.android.com/reference/android/media/MediaPlayer. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: 'android.media.MediaPlayer', +// ), +// ) +// abstract class MediaPlayer { +// /// Gets the duration of the file. +// int getDuration(); +// +// /// Seeks to specified time position. +// void seekTo(int mSec); +// +// /// Starts or resumes playback. +// void start(); +// +// /// Pauses playback. +// void pause(); +// +// /// Stops playback after playback has been started or paused. +// void stop(); +// } +// +// /// Callbacks that the player must fire. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoAdPlayer.VideoAdPlayerCallback.html +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback', +// ), +// ) +// abstract class VideoAdPlayerCallback { +// /// Fire this callback periodically as ad playback occurs. +// void onAdProgress( +// AdMediaInfo adMediaInfo, +// VideoProgressUpdate videoProgressUpdate, +// ); +// +// /// Fire this callback when video playback stalls waiting for data. +// void onBuffering(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when all content has finished playing. +// void onContentComplete(); +// +// /// Fire this callback when the video finishes playing. +// void onEnded(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when the video has encountered an error. +// void onError(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when the video is ready to begin playback. +// void onLoaded(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when the video is paused. +// void onPause(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when the player begins playing a video. +// void onPlay(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when the video is unpaused. +// void onResume(AdMediaInfo adMediaInfo); +// +// /// Fire this callback when the playback volume changes. +// void onVolumeChanged(AdMediaInfo adMediaInfo, int percentage); +// } +// +// /// Defines the set of methods that a video player must implement to be used by +// /// the IMA SDK, as well as a set of callbacks that it must fire. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/player/VideoAdPlayer.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer', +// ), +// ) +// abstract class VideoAdPlayer { +// VideoAdPlayer(); +// +// /// Adds a callback. +// late final void Function(VideoAdPlayerCallback callback) addCallback; +// +// /// Loads a video ad hosted at AdMediaInfo. +// late final void Function(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) loadAd; +// +// /// Pauses playing the current ad. +// late final void Function(AdMediaInfo adMediaInfo) pauseAd; +// +// /// Starts or resumes playing the video ad referenced by the AdMediaInfo, +// /// provided loadAd has already been called for it. +// late final void Function(AdMediaInfo adMediaInfo) playAd; +// +// /// Cleans up and releases all resources used by the `VideoAdPlayer`. +// late final void Function() release; +// +// /// Removes a callback. +// late final void Function(VideoAdPlayerCallback callback) removeCallback; +// +// /// Stops playing the current ad. +// late final void Function(AdMediaInfo adMediaInfo) stopAd; +// +// /// The volume of the player as a percentage from 0 to 100. +// void setVolume(int value); +// +// /// The `VideoProgressUpdate` describing playback progress of the current +// /// video. +// void setAdProgress(VideoProgressUpdate progress); +// } +// +// /// Listener interface for notification of ad load or stream load completion. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdsLoader.AdsLoadedListener.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener', +// ), +// ) +// abstract class AdsLoadedListener { +// AdsLoadedListener(); +// +// /// Called once the AdsManager or StreamManager has been loaded. +// late final void Function(AdsManagerLoadedEvent event) onAdsManagerLoaded; +// } +// +// /// Interface for classes that will listen to AdErrorEvents. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdErrorEvent.AdErrorListener.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener', +// ), +// ) +// abstract class AdErrorListener { +// AdErrorListener(); +// +// /// Called when an error occurs. +// late final void Function(AdErrorEvent event) onAdError; +// } +// +// /// Listener interface for ad events. +// /// +// /// See https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/AdEvent.AdEventListener.html. +// @ProxyApi( +// kotlinOptions: KotlinProxyApiOptions( +// fullClassName: +// 'com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener', +// ), +// ) +// abstract class AdEventListener { +// AdEventListener(); +// +// /// Respond to an occurrence of an AdEvent. +// late final void Function(AdEvent event) onAdEvent; +// } diff --git a/packages/interactive_media_ads/pubspec.yaml b/packages/interactive_media_ads/pubspec.yaml index a6b7b04a214..99fa8785dc8 100644 --- a/packages/interactive_media_ads/pubspec.yaml +++ b/packages/interactive_media_ads/pubspec.yaml @@ -2,7 +2,7 @@ name: interactive_media_ads description: A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. repository: https://github.com/flutter/packages/tree/main/packages/interactive_media_ads issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+interactive_media_ads%22 -version: 0.0.1 +version: 0.0.2+1 # This must match the version in `android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt` environment: sdk: ^3.2.3 @@ -14,12 +14,14 @@ flutter: android: package: dev.flutter.packages.interactive_media_ads pluginClass: InteractiveMediaAdsPlugin + dartPluginClass: AndroidInteractiveMediaAds ios: pluginClass: InteractiveMediaAdsPlugin dependencies: flutter: sdk: flutter + meta: ^1.10.0 dev_dependencies: build_runner: ^2.1.4 diff --git a/packages/interactive_media_ads/test/android/ad_display_container_test.dart b/packages/interactive_media_ads/test/android/ad_display_container_test.dart new file mode 100644 index 00000000000..83694918bf3 --- /dev/null +++ b/packages/interactive_media_ads/test/android/ad_display_container_test.dart @@ -0,0 +1,518 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/src/android/android_ad_display_container.dart'; +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as ima; +import 'package:interactive_media_ads/src/android/interactive_media_ads_proxy.dart'; +import 'package:interactive_media_ads/src/android/platform_views_service_proxy.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'ad_display_container_test.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AndroidAdDisplayContainer', () { + testWidgets('build with key', (WidgetTester tester) async { + final AndroidAdDisplayContainer container = AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + key: const Key('testKey'), + onContainerAdded: (_) {}, + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) => container.build(context), + )); + + expect(find.byType(PlatformViewLink), findsOneWidget); + expect(find.byKey(const Key('testKey')), findsOneWidget); + }); + + testWidgets('onContainerAdded is called', (WidgetTester tester) async { + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + required dynamic onError, + dynamic onPrepared, + dynamic onCompletion, + }) => + MockVideoView(), + createAdDisplayContainerImaSdkFactory: ( + _, + __, + ) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required dynamic addCallback, + required dynamic loadAd, + required dynamic pauseAd, + required dynamic playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) => + MockVideoAdPlayer(), + ); + + final MockPlatformViewsServiceProxy mockPlatformViewsProxy = + MockPlatformViewsServiceProxy(); + final MockSurfaceAndroidViewController mockAndroidViewController = + MockSurfaceAndroidViewController(); + + late final int platformViewId; + when( + mockPlatformViewsProxy.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenAnswer((Invocation invocation) { + platformViewId = invocation.namedArguments[const Symbol('id')] as int; + return mockAndroidViewController; + }); + + final AndroidAdDisplayContainer container = AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: expectAsync1((_) {}), + platformViewsProxy: mockPlatformViewsProxy, + imaProxy: imaProxy, + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) => container.build(context), + )); + + final void Function(int) onPlatformCreatedCallback = verify( + mockAndroidViewController + .addOnPlatformViewCreatedListener(captureAny)) + .captured[0] as void Function(int); + + onPlatformCreatedCallback(platformViewId); + + await tester.pumpAndSettle(); + }); + + test('completing the ad notifies IMA SDK the ad has ended', () { + late final void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAdCallback; + + late final void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallbackCallback; + + late final void Function( + ima.VideoView, + ima.MediaPlayer, + ) onCompletionCallback; + + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + required dynamic onError, + dynamic onPrepared, + void Function( + ima.VideoView, + ima.MediaPlayer, + )? onCompletion, + }) { + onCompletionCallback = onCompletion!; + return MockVideoView(); + }, + createAdDisplayContainerImaSdkFactory: ( + _, + __, + ) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallback, + required void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAd, + required dynamic pauseAd, + required dynamic playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) { + loadAdCallback = loadAd; + addCallbackCallback = addCallback; + return MockVideoAdPlayer(); + }, + ); + + AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + imaProxy: imaProxy, + ), + ); + + final ima.AdMediaInfo mockAdMediaInfo = MockAdMediaInfo(); + loadAdCallback(MockVideoAdPlayer(), mockAdMediaInfo, MockAdPodInfo()); + + final MockVideoAdPlayerCallback mockPlayerCallback = + MockVideoAdPlayerCallback(); + addCallbackCallback(MockVideoAdPlayer(), mockPlayerCallback); + + onCompletionCallback(MockVideoView(), MockMediaPlayer()); + + verify(mockPlayerCallback.onEnded(mockAdMediaInfo)); + }); + + test('error loading the ad notifies IMA SDK of error', () { + late final void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAdCallback; + + late final void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallbackCallback; + + late final void Function( + ima.VideoView, + ima.MediaPlayer, + int, + int, + ) onErrorCallback; + + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + required void Function( + ima.VideoView, + ima.MediaPlayer, + int, + int, + ) onError, + dynamic onPrepared, + dynamic onCompletion, + }) { + onErrorCallback = onError; + return MockVideoView(); + }, + createAdDisplayContainerImaSdkFactory: ( + _, + __, + ) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallback, + required void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAd, + required dynamic pauseAd, + required dynamic playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) { + loadAdCallback = loadAd; + addCallbackCallback = addCallback; + return MockVideoAdPlayer(); + }, + ); + + AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + imaProxy: imaProxy, + ), + ); + + final ima.AdMediaInfo mockAdMediaInfo = MockAdMediaInfo(); + loadAdCallback(MockVideoAdPlayer(), mockAdMediaInfo, MockAdPodInfo()); + + final MockVideoAdPlayerCallback mockPlayerCallback = + MockVideoAdPlayerCallback(); + addCallbackCallback(MockVideoAdPlayer(), mockPlayerCallback); + + onErrorCallback(MockVideoView(), MockMediaPlayer(), 0, 0); + + verify(mockPlayerCallback.onError(mockAdMediaInfo)); + }); + + test('play ad once when it is prepared', () async { + late final void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAdCallback; + + late final void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallbackCallback; + + late final Future Function( + ima.VideoView, + ima.MediaPlayer, + ) onPreparedCallback; + + const int adDuration = 100; + const int adProgress = 10; + + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + dynamic onError, + dynamic onPrepared, + dynamic onCompletion, + }) { + // VideoView.onPrepared returns void, but the implementation uses an + // async callback method. + onPreparedCallback = onPrepared! as Future Function( + ima.VideoView, + ima.MediaPlayer, + ); + final MockVideoView mockVideoView = MockVideoView(); + when(mockVideoView.getCurrentPosition()).thenAnswer( + (_) async => adProgress, + ); + return mockVideoView; + }, + createAdDisplayContainerImaSdkFactory: (_, __) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallback, + required void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAd, + required dynamic pauseAd, + required dynamic playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) { + loadAdCallback = loadAd; + addCallbackCallback = addCallback; + return MockVideoAdPlayer(); + }, + newVideoProgressUpdate: ({ + required int currentTimeMs, + required int durationMs, + }) { + expect(currentTimeMs, adProgress); + expect(durationMs, adDuration); + return MockVideoProgressUpdate(); + }, + ); + + AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + imaProxy: imaProxy, + ), + ); + + final ima.AdMediaInfo mockAdMediaInfo = MockAdMediaInfo(); + loadAdCallback(MockVideoAdPlayer(), mockAdMediaInfo, MockAdPodInfo()); + + final MockVideoAdPlayerCallback mockPlayerCallback = + MockVideoAdPlayerCallback(); + addCallbackCallback(MockVideoAdPlayer(), mockPlayerCallback); + + final MockMediaPlayer mockMediaPlayer = MockMediaPlayer(); + when(mockMediaPlayer.getDuration()).thenAnswer((_) async => adDuration); + + await onPreparedCallback(MockVideoView(), mockMediaPlayer); + + verify(mockMediaPlayer.start()); + + // Ad progress is updated with a reoccurring timer, so this waits for + // at least one update. + await Future.delayed(const Duration(milliseconds: 300)); + verify(mockPlayerCallback.onAdProgress(mockAdMediaInfo, any)); + }); + + test('pause ad', () async { + late final void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAdCallback; + + late final Future Function( + ima.VideoView, + ima.MediaPlayer, + ) onPreparedCallback; + + late final Future Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ) pauseAdCallback; + + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + dynamic onError, + void Function( + ima.VideoView, + ima.MediaPlayer, + )? onPrepared, + dynamic onCompletion, + }) { + // VideoView.onPrepared returns void, but the implementation uses an + // async callback method. + onPreparedCallback = onPrepared! as Future Function( + ima.VideoView, + ima.MediaPlayer, + ); + final MockVideoView mockVideoView = MockVideoView(); + when(mockVideoView.getCurrentPosition()).thenAnswer((_) async => 10); + return mockVideoView; + }, + createAdDisplayContainerImaSdkFactory: (_, __) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required dynamic addCallback, + required void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ima.AdPodInfo, + ) loadAd, + required dynamic pauseAd, + required dynamic playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) { + loadAdCallback = loadAd; + // VideoAdPlayer.pauseAd returns void, but the implementation uses an + // async callback method. + pauseAdCallback = pauseAd as Future Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ); + return MockVideoAdPlayer(); + }, + newVideoProgressUpdate: ({ + required int currentTimeMs, + required int durationMs, + }) { + return MockVideoProgressUpdate(); + }, + ); + + AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + imaProxy: imaProxy, + ), + ); + + final ima.AdMediaInfo mockAdMediaInfo = MockAdMediaInfo(); + loadAdCallback(MockVideoAdPlayer(), mockAdMediaInfo, MockAdPodInfo()); + + final MockMediaPlayer mockMediaPlayer = MockMediaPlayer(); + when(mockMediaPlayer.getDuration()).thenAnswer((_) async => 100); + + await onPreparedCallback(MockVideoView(), mockMediaPlayer); + + await pauseAdCallback(MockVideoAdPlayer(), mockAdMediaInfo); + + verify(mockMediaPlayer.pause()); + }); + + test('play ad', () async { + late final void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ) playAdCallback; + + final MockVideoView mockVideoView = MockVideoView(); + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + dynamic onError, + dynamic onPrepared, + dynamic onCompletion, + }) { + return mockVideoView; + }, + createAdDisplayContainerImaSdkFactory: (_, __) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required dynamic addCallback, + required dynamic loadAd, + required dynamic pauseAd, + required void Function( + ima.VideoAdPlayer, + ima.AdMediaInfo, + ) playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) { + playAdCallback = playAd; + return MockVideoAdPlayer(); + }, + ); + + AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: (_) {}, + imaProxy: imaProxy, + ), + ); + + const String videoUrl = 'url'; + final ima.AdMediaInfo mockAdMediaInfo = MockAdMediaInfo(); + when(mockAdMediaInfo.url).thenReturn(videoUrl); + playAdCallback(MockVideoAdPlayer(), mockAdMediaInfo); + + verify(mockVideoView.setVideoUri(videoUrl)); + }); + }); +} diff --git a/packages/interactive_media_ads/test/android/ad_display_container_test.mocks.dart b/packages/interactive_media_ads/test/android/ad_display_container_test.mocks.dart new file mode 100644 index 00000000000..ef3f12b50b0 --- /dev/null +++ b/packages/interactive_media_ads/test/android/ad_display_container_test.mocks.dart @@ -0,0 +1,1266 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in interactive_media_ads/test/android/ad_display_container_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; +import 'dart:ui' as _i3; + +import 'package:flutter/services.dart' as _i4; +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as _i2; +import 'package:interactive_media_ads/src/android/platform_views_service_proxy.dart' + as _i7; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdDisplayContainer_1 extends _i1.SmartFake + implements _i2.AdDisplayContainer { + _FakeAdDisplayContainer_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdMediaInfo_2 extends _i1.SmartFake implements _i2.AdMediaInfo { + _FakeAdMediaInfo_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdPodInfo_3 extends _i1.SmartFake implements _i2.AdPodInfo { + _FakeAdPodInfo_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFrameLayout_4 extends _i1.SmartFake implements _i2.FrameLayout { + _FakeFrameLayout_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMediaPlayer_5 extends _i1.SmartFake implements _i2.MediaPlayer { + _FakeMediaPlayer_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoAdPlayer_6 extends _i1.SmartFake implements _i2.VideoAdPlayer { + _FakeVideoAdPlayer_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoAdPlayerCallback_7 extends _i1.SmartFake + implements _i2.VideoAdPlayerCallback { + _FakeVideoAdPlayerCallback_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoProgressUpdate_8 extends _i1.SmartFake + implements _i2.VideoProgressUpdate { + _FakeVideoProgressUpdate_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoView_9 extends _i1.SmartFake implements _i2.VideoView { + _FakeVideoView_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_10 extends _i1.SmartFake implements _i3.Offset { + _FakeOffset_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSize_11 extends _i1.SmartFake implements _i3.Size { + _FakeSize_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeExpensiveAndroidViewController_12 extends _i1.SmartFake + implements _i4.ExpensiveAndroidViewController { + _FakeExpensiveAndroidViewController_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSurfaceAndroidViewController_13 extends _i1.SmartFake + implements _i4.SurfaceAndroidViewController { + _FakeSurfaceAndroidViewController_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AdDisplayContainer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdDisplayContainer extends _i1.Mock + implements _i2.AdDisplayContainer { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdDisplayContainer pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdDisplayContainer_1( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdDisplayContainer_1( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdDisplayContainer); +} + +/// A class which mocks [AdMediaInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdMediaInfo extends _i1.Mock implements _i2.AdMediaInfo { + @override + String get url => (super.noSuchMethod( + Invocation.getter(#url), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#url), + ), + returnValueForMissingStub: _i5.dummyValue( + this, + Invocation.getter(#url), + ), + ) as String); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdMediaInfo pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdMediaInfo_2( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdMediaInfo_2( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdMediaInfo); +} + +/// A class which mocks [AdPodInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdPodInfo extends _i1.Mock implements _i2.AdPodInfo { + @override + int get adPosition => (super.noSuchMethod( + Invocation.getter(#adPosition), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + double get maxDuration => (super.noSuchMethod( + Invocation.getter(#maxDuration), + returnValue: 0.0, + returnValueForMissingStub: 0.0, + ) as double); + + @override + int get podIndex => (super.noSuchMethod( + Invocation.getter(#podIndex), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + double get timeOffset => (super.noSuchMethod( + Invocation.getter(#timeOffset), + returnValue: 0.0, + returnValueForMissingStub: 0.0, + ) as double); + + @override + int get totalAds => (super.noSuchMethod( + Invocation.getter(#totalAds), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + bool get isBumper => (super.noSuchMethod( + Invocation.getter(#isBumper), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdPodInfo pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdPodInfo_3( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdPodInfo_3( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdPodInfo); +} + +/// A class which mocks [FrameLayout]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFrameLayout extends _i1.Mock implements _i2.FrameLayout { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.FrameLayout pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeFrameLayout_4( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeFrameLayout_4( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.FrameLayout); + + @override + _i6.Future addView(_i2.View? view) => (super.noSuchMethod( + Invocation.method( + #addView, + [view], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [MediaPlayer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMediaPlayer extends _i1.Mock implements _i2.MediaPlayer { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future getDuration() => (super.noSuchMethod( + Invocation.method( + #getDuration, + [], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i6.Future seekTo(int? mSec) => (super.noSuchMethod( + Invocation.method( + #seekTo, + [mSec], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future pause() => (super.noSuchMethod( + Invocation.method( + #pause, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future stop() => (super.noSuchMethod( + Invocation.method( + #stop, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.MediaPlayer pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeMediaPlayer_5( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeMediaPlayer_5( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.MediaPlayer); +} + +/// A class which mocks [VideoAdPlayer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoAdPlayer extends _i1.Mock implements _i2.VideoAdPlayer { + @override + void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + ) get addCallback => (super.noSuchMethod( + Invocation.getter(#addCallback), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + _i2.AdPodInfo, + ) get loadAd => (super.noSuchMethod( + Invocation.getter(#loadAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + _i2.AdPodInfo adPodInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + _i2.AdPodInfo adPodInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + _i2.AdPodInfo, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + ) get pauseAd => (super.noSuchMethod( + Invocation.getter(#pauseAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + ) get playAd => (super.noSuchMethod( + Invocation.getter(#playAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + )); + + @override + void Function(_i2.VideoAdPlayer) get release => (super.noSuchMethod( + Invocation.getter(#release), + returnValue: (_i2.VideoAdPlayer pigeon_instance) {}, + returnValueForMissingStub: (_i2.VideoAdPlayer pigeon_instance) {}, + ) as void Function(_i2.VideoAdPlayer)); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + ) get removeCallback => (super.noSuchMethod( + Invocation.getter(#removeCallback), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + ) get stopAd => (super.noSuchMethod( + Invocation.getter(#stopAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future setVolume(int? value) => (super.noSuchMethod( + Invocation.method( + #setVolume, + [value], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setAdProgress(_i2.VideoProgressUpdate? progress) => + (super.noSuchMethod( + Invocation.method( + #setAdProgress, + [progress], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.VideoAdPlayer pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoAdPlayer_6( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoAdPlayer_6( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoAdPlayer); +} + +/// A class which mocks [VideoAdPlayerCallback]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoAdPlayerCallback extends _i1.Mock + implements _i2.VideoAdPlayerCallback { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future onAdProgress( + _i2.AdMediaInfo? adMediaInfo, + _i2.VideoProgressUpdate? videoProgressUpdate, + ) => + (super.noSuchMethod( + Invocation.method( + #onAdProgress, + [ + adMediaInfo, + videoProgressUpdate, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onBuffering(_i2.AdMediaInfo? adMediaInfo) => + (super.noSuchMethod( + Invocation.method( + #onBuffering, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onContentComplete() => (super.noSuchMethod( + Invocation.method( + #onContentComplete, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onEnded(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onEnded, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onError(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onError, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onLoaded(_i2.AdMediaInfo? adMediaInfo) => + (super.noSuchMethod( + Invocation.method( + #onLoaded, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onPause(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onPause, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onPlay(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onPlay, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onResume(_i2.AdMediaInfo? adMediaInfo) => + (super.noSuchMethod( + Invocation.method( + #onResume, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onVolumeChanged( + _i2.AdMediaInfo? adMediaInfo, + int? percentage, + ) => + (super.noSuchMethod( + Invocation.method( + #onVolumeChanged, + [ + adMediaInfo, + percentage, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.VideoAdPlayerCallback pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoAdPlayerCallback_7( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoAdPlayerCallback_7( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoAdPlayerCallback); +} + +/// A class which mocks [VideoProgressUpdate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoProgressUpdate extends _i1.Mock + implements _i2.VideoProgressUpdate { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.VideoProgressUpdate pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoProgressUpdate_8( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoProgressUpdate_8( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoProgressUpdate); +} + +/// A class which mocks [VideoView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoView extends _i1.Mock implements _i2.VideoView { + @override + void Function( + _i2.VideoView, + _i2.MediaPlayer, + int, + int, + ) get onError => (super.noSuchMethod( + Invocation.getter(#onError), + returnValue: ( + _i2.VideoView pigeon_instance, + _i2.MediaPlayer player, + int what, + int extra, + ) {}, + returnValueForMissingStub: ( + _i2.VideoView pigeon_instance, + _i2.MediaPlayer player, + int what, + int extra, + ) {}, + ) as void Function( + _i2.VideoView, + _i2.MediaPlayer, + int, + int, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future setVideoUri(String? uri) => (super.noSuchMethod( + Invocation.method( + #setVideoUri, + [uri], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getCurrentPosition() => (super.noSuchMethod( + Invocation.method( + #getCurrentPosition, + [], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i2.VideoView pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoView_9( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoView_9( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoView); +} + +/// A class which mocks [SurfaceAndroidViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSurfaceAndroidViewController extends _i1.Mock + implements _i4.SurfaceAndroidViewController { + @override + bool get requiresViewComposition => (super.noSuchMethod( + Invocation.getter(#requiresViewComposition), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + int get viewId => (super.noSuchMethod( + Invocation.getter(#viewId), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + bool get awaitingCreation => (super.noSuchMethod( + Invocation.getter(#awaitingCreation), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i4.PointTransformer get pointTransformer => (super.noSuchMethod( + Invocation.getter(#pointTransformer), + returnValue: (_i3.Offset position) => _FakeOffset_10( + this, + Invocation.getter(#pointTransformer), + ), + returnValueForMissingStub: (_i3.Offset position) => _FakeOffset_10( + this, + Invocation.getter(#pointTransformer), + ), + ) as _i4.PointTransformer); + + @override + set pointTransformer(_i4.PointTransformer? transformer) => super.noSuchMethod( + Invocation.setter( + #pointTransformer, + transformer, + ), + returnValueForMissingStub: null, + ); + + @override + bool get isCreated => (super.noSuchMethod( + Invocation.getter(#isCreated), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + List<_i4.PlatformViewCreatedCallback> get createdCallbacks => + (super.noSuchMethod( + Invocation.getter(#createdCallbacks), + returnValue: <_i4.PlatformViewCreatedCallback>[], + returnValueForMissingStub: <_i4.PlatformViewCreatedCallback>[], + ) as List<_i4.PlatformViewCreatedCallback>); + + @override + _i6.Future setOffset(_i3.Offset? off) => (super.noSuchMethod( + Invocation.method( + #setOffset, + [off], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future create({ + _i3.Size? size, + _i3.Offset? position, + }) => + (super.noSuchMethod( + Invocation.method( + #create, + [], + { + #size: size, + #position: position, + }, + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i3.Size> setSize(_i3.Size? size) => (super.noSuchMethod( + Invocation.method( + #setSize, + [size], + ), + returnValue: _i6.Future<_i3.Size>.value(_FakeSize_11( + this, + Invocation.method( + #setSize, + [size], + ), + )), + returnValueForMissingStub: _i6.Future<_i3.Size>.value(_FakeSize_11( + this, + Invocation.method( + #setSize, + [size], + ), + )), + ) as _i6.Future<_i3.Size>); + + @override + _i6.Future sendMotionEvent(_i4.AndroidMotionEvent? event) => + (super.noSuchMethod( + Invocation.method( + #sendMotionEvent, + [event], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + void addOnPlatformViewCreatedListener( + _i4.PlatformViewCreatedCallback? listener) => + super.noSuchMethod( + Invocation.method( + #addOnPlatformViewCreatedListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeOnPlatformViewCreatedListener( + _i4.PlatformViewCreatedCallback? listener) => + super.noSuchMethod( + Invocation.method( + #removeOnPlatformViewCreatedListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + _i6.Future setLayoutDirection(_i3.TextDirection? layoutDirection) => + (super.noSuchMethod( + Invocation.method( + #setLayoutDirection, + [layoutDirection], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future dispatchPointerEvent(_i4.PointerEvent? event) => + (super.noSuchMethod( + Invocation.method( + #dispatchPointerEvent, + [event], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future clearFocus() => (super.noSuchMethod( + Invocation.method( + #clearFocus, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future dispose() => (super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [PlatformViewsServiceProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockPlatformViewsServiceProxy extends _i1.Mock + implements _i7.PlatformViewsServiceProxy { + @override + _i4.ExpensiveAndroidViewController initExpensiveAndroidView({ + required int? id, + required String? viewType, + required _i3.TextDirection? layoutDirection, + dynamic creationParams, + _i4.MessageCodec? creationParamsCodec, + _i3.VoidCallback? onFocus, + }) => + (super.noSuchMethod( + Invocation.method( + #initExpensiveAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + returnValue: _FakeExpensiveAndroidViewController_12( + this, + Invocation.method( + #initExpensiveAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + returnValueForMissingStub: _FakeExpensiveAndroidViewController_12( + this, + Invocation.method( + #initExpensiveAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + ) as _i4.ExpensiveAndroidViewController); + + @override + _i4.SurfaceAndroidViewController initSurfaceAndroidView({ + required int? id, + required String? viewType, + required _i3.TextDirection? layoutDirection, + dynamic creationParams, + _i4.MessageCodec? creationParamsCodec, + _i3.VoidCallback? onFocus, + }) => + (super.noSuchMethod( + Invocation.method( + #initSurfaceAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + returnValue: _FakeSurfaceAndroidViewController_13( + this, + Invocation.method( + #initSurfaceAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + returnValueForMissingStub: _FakeSurfaceAndroidViewController_13( + this, + Invocation.method( + #initSurfaceAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + ) as _i4.SurfaceAndroidViewController); +} diff --git a/packages/interactive_media_ads/test/android/ads_loader_test.dart b/packages/interactive_media_ads/test/android/ads_loader_test.dart new file mode 100644 index 00000000000..3446c4e899a --- /dev/null +++ b/packages/interactive_media_ads/test/android/ads_loader_test.dart @@ -0,0 +1,322 @@ +// 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:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/src/android/android_ad_display_container.dart'; +import 'package:interactive_media_ads/src/android/android_ads_loader.dart'; +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as ima; +import 'package:interactive_media_ads/src/android/interactive_media_ads_proxy.dart'; +import 'package:interactive_media_ads/src/android/platform_views_service_proxy.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'ads_loader_test.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + group('AndroidAdsLoader', () { + testWidgets('instantiate AndroidAdsLoader', (WidgetTester tester) async { + final AndroidAdDisplayContainer container = + await _pumpAdDisplayContainer(tester); + + AndroidAdsLoader( + AndroidAdsLoaderCreationParams( + container: container, + onAdsLoaded: (PlatformOnAdsLoadedData data) {}, + onAdsLoadError: (AdsLoadErrorData data) {}, + ), + ); + }); + + testWidgets('contentComplete', (WidgetTester tester) async { + final MockVideoAdPlayerCallback mockAdPlayerCallback = + MockVideoAdPlayerCallback(); + final AndroidAdDisplayContainer container = await _pumpAdDisplayContainer( + tester, + mockAdPlayerCallback: mockAdPlayerCallback, + ); + + final AndroidAdsLoader loader = AndroidAdsLoader( + AndroidAdsLoaderCreationParams( + container: container, + onAdsLoaded: (PlatformOnAdsLoadedData data) {}, + onAdsLoadError: (AdsLoadErrorData data) {}, + ), + ); + + await loader.contentComplete(); + verify(mockAdPlayerCallback.onContentComplete()); + }); + + testWidgets('requestAds', (WidgetTester tester) async { + final AndroidAdDisplayContainer container = + await _pumpAdDisplayContainer(tester); + + final MockImaSdkFactory mockSdkFactory = MockImaSdkFactory(); + when(mockSdkFactory.createImaSdkSettings()).thenAnswer((_) async { + return MockImaSdkSettings(); + }); + + final MockAdsLoader mockAdsLoader = MockAdsLoader(); + when(mockSdkFactory.createAdsLoader(any, any)).thenAnswer((_) async { + return mockAdsLoader; + }); + + final MockAdsRequest mockAdsRequest = MockAdsRequest(); + when(mockSdkFactory.createAdsRequest()).thenAnswer((_) async { + return mockAdsRequest; + }); + + final InteractiveMediaAdsProxy proxy = InteractiveMediaAdsProxy( + instanceImaSdkFactory: () => mockSdkFactory, + ); + + final AndroidAdsLoader adsLoader = AndroidAdsLoader( + AndroidAdsLoaderCreationParams( + container: container, + onAdsLoaded: (PlatformOnAdsLoadedData data) {}, + onAdsLoadError: (AdsLoadErrorData data) {}, + proxy: proxy, + ), + ); + + await adsLoader.requestAds(AdsRequest(adTagUrl: 'url')); + + verifyInOrder(>[ + mockAdsRequest.setAdTagUrl('url'), + mockAdsLoader.requestAds(mockAdsRequest), + ]); + }); + + testWidgets('onAdsLoaded', (WidgetTester tester) async { + final AndroidAdDisplayContainer container = + await _pumpAdDisplayContainer(tester); + + final MockImaSdkFactory mockSdkFactory = MockImaSdkFactory(); + when(mockSdkFactory.createImaSdkSettings()).thenAnswer((_) async { + return MockImaSdkSettings(); + }); + + final MockAdsLoader mockAdsLoader = MockAdsLoader(); + final Completer addEventListenerCompleter = Completer(); + when(mockAdsLoader.addAdsLoadedListener(any)).thenAnswer((_) async { + addEventListenerCompleter.complete(); + }); + + when(mockSdkFactory.createAdsLoader(any, any)).thenAnswer((_) async { + return mockAdsLoader; + }); + + late final void Function( + ima.AdsLoadedListener, + ima.AdsManagerLoadedEvent, + ) onAdsManagerLoadedCallback; + + final InteractiveMediaAdsProxy proxy = InteractiveMediaAdsProxy( + instanceImaSdkFactory: () => mockSdkFactory, + newAdsLoadedListener: ({ + required void Function( + ima.AdsLoadedListener, + ima.AdsManagerLoadedEvent, + ) onAdsManagerLoaded, + }) { + onAdsManagerLoadedCallback = onAdsManagerLoaded; + return MockAdsLoadedListener(); + }, + newAdErrorListener: ({required dynamic onAdError}) { + return MockAdErrorListener(); + }, + ); + + AndroidAdsLoader( + AndroidAdsLoaderCreationParams( + container: container, + onAdsLoaded: expectAsync1((_) {}), + onAdsLoadError: (_) {}, + proxy: proxy, + ), + ); + + final MockAdsManagerLoadedEvent mockLoadedEvent = + MockAdsManagerLoadedEvent(); + when(mockLoadedEvent.manager).thenReturn(MockAdsManager()); + + await addEventListenerCompleter.future; + + onAdsManagerLoadedCallback(MockAdsLoadedListener(), mockLoadedEvent); + }); + + testWidgets('onAdError', (WidgetTester tester) async { + final AndroidAdDisplayContainer container = + await _pumpAdDisplayContainer(tester); + + final MockImaSdkFactory mockSdkFactory = MockImaSdkFactory(); + when(mockSdkFactory.createImaSdkSettings()).thenAnswer((_) async { + return MockImaSdkSettings(); + }); + + final MockAdsLoader mockAdsLoader = MockAdsLoader(); + final Completer addErrorListenerCompleter = Completer(); + when(mockAdsLoader.addAdErrorListener(any)).thenAnswer((_) async { + addErrorListenerCompleter.complete(); + }); + + when(mockSdkFactory.createAdsLoader(any, any)).thenAnswer((_) async { + return mockAdsLoader; + }); + + late final void Function( + ima.AdErrorListener, + ima.AdErrorEvent, + ) onAdErrorCallback; + + final InteractiveMediaAdsProxy proxy = InteractiveMediaAdsProxy( + instanceImaSdkFactory: () => mockSdkFactory, + newAdsLoadedListener: ({required dynamic onAdsManagerLoaded}) { + return MockAdsLoadedListener(); + }, + newAdErrorListener: ({ + required void Function( + ima.AdErrorListener, + ima.AdErrorEvent, + ) onAdError, + }) { + onAdErrorCallback = onAdError; + return MockAdErrorListener(); + }, + ); + + AndroidAdsLoader( + AndroidAdsLoaderCreationParams( + container: container, + onAdsLoaded: (_) {}, + onAdsLoadError: expectAsync1((_) {}), + proxy: proxy, + ), + ); + + final MockAdErrorEvent mockErrorEvent = MockAdErrorEvent(); + final MockAdError mockError = MockAdError(); + when(mockError.errorType).thenReturn(ima.AdErrorType.load); + when(mockError.errorCode) + .thenReturn(ima.AdErrorCode.adsRequestNetworkError); + when(mockError.message).thenReturn('error message'); + when(mockErrorEvent.error).thenReturn(mockError); + + await addErrorListenerCompleter.future; + + onAdErrorCallback(MockAdErrorListener(), mockErrorEvent); + }); + }); +} + +Future _pumpAdDisplayContainer( + WidgetTester tester, { + MockVideoAdPlayerCallback? mockAdPlayerCallback, +}) async { + final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy( + newFrameLayout: () => MockFrameLayout(), + newVideoView: ({ + required dynamic onError, + dynamic onPrepared, + dynamic onCompletion, + }) => + MockVideoView(), + createAdDisplayContainerImaSdkFactory: ( + _, + __, + ) async { + return MockAdDisplayContainer(); + }, + newVideoAdPlayer: ({ + required void Function( + ima.VideoAdPlayer, + ima.VideoAdPlayerCallback, + ) addCallback, + required dynamic loadAd, + required dynamic pauseAd, + required dynamic playAd, + required dynamic release, + required dynamic removeCallback, + required dynamic stopAd, + }) { + if (mockAdPlayerCallback != null) { + addCallback(MockVideoAdPlayer(), mockAdPlayerCallback); + } + return MockVideoAdPlayer(); + }, + ); + + final MockPlatformViewsServiceProxy mockPlatformViewsProxy = + MockPlatformViewsServiceProxy(); + final MockSurfaceAndroidViewController mockAndroidViewController = + MockSurfaceAndroidViewController(); + + late final int platformViewId; + when( + mockPlatformViewsProxy.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenAnswer((Invocation invocation) { + platformViewId = invocation.namedArguments[const Symbol('id')] as int; + return mockAndroidViewController; + }); + + final Completer adDisplayContainerCompleter = + Completer(); + + final AndroidAdDisplayContainer container = AndroidAdDisplayContainer( + AndroidAdDisplayContainerCreationParams( + onContainerAdded: (PlatformAdDisplayContainer container) { + adDisplayContainerCompleter.complete( + container as AndroidAdDisplayContainer, + ); + }, + platformViewsProxy: mockPlatformViewsProxy, + imaProxy: imaProxy, + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) => container.build(context), + )); + + final void Function(int) onPlatformCreatedCallback = verify( + mockAndroidViewController + .addOnPlatformViewCreatedListener(captureAny)) + .captured[0] as void Function(int); + + onPlatformCreatedCallback(platformViewId); + + return adDisplayContainerCompleter.future; +} diff --git a/packages/interactive_media_ads/test/android/ads_loader_test.mocks.dart b/packages/interactive_media_ads/test/android/ads_loader_test.mocks.dart new file mode 100644 index 00000000000..bdcfc1f4d54 --- /dev/null +++ b/packages/interactive_media_ads/test/android/ads_loader_test.mocks.dart @@ -0,0 +1,1771 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in interactive_media_ads/test/android/ads_loader_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; +import 'dart:ui' as _i3; + +import 'package:flutter/services.dart' as _i4; +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as _i2; +import 'package:interactive_media_ads/src/android/platform_views_service_proxy.dart' + as _i7; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdDisplayContainer_1 extends _i1.SmartFake + implements _i2.AdDisplayContainer { + _FakeAdDisplayContainer_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdError_2 extends _i1.SmartFake implements _i2.AdError { + _FakeAdError_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdErrorEvent_3 extends _i1.SmartFake implements _i2.AdErrorEvent { + _FakeAdErrorEvent_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdErrorListener_4 extends _i1.SmartFake + implements _i2.AdErrorListener { + _FakeAdErrorListener_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdsLoadedListener_5 extends _i1.SmartFake + implements _i2.AdsLoadedListener { + _FakeAdsLoadedListener_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdsManager_6 extends _i1.SmartFake implements _i2.AdsManager { + _FakeAdsManager_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdsManagerLoadedEvent_7 extends _i1.SmartFake + implements _i2.AdsManagerLoadedEvent { + _FakeAdsManagerLoadedEvent_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdsLoader_8 extends _i1.SmartFake implements _i2.AdsLoader { + _FakeAdsLoader_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdsRequest_9 extends _i1.SmartFake implements _i2.AdsRequest { + _FakeAdsRequest_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFrameLayout_10 extends _i1.SmartFake implements _i2.FrameLayout { + _FakeFrameLayout_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeImaSdkSettings_11 extends _i1.SmartFake + implements _i2.ImaSdkSettings { + _FakeImaSdkSettings_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeImaSdkFactory_12 extends _i1.SmartFake implements _i2.ImaSdkFactory { + _FakeImaSdkFactory_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoAdPlayer_13 extends _i1.SmartFake implements _i2.VideoAdPlayer { + _FakeVideoAdPlayer_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoAdPlayerCallback_14 extends _i1.SmartFake + implements _i2.VideoAdPlayerCallback { + _FakeVideoAdPlayerCallback_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeVideoView_15 extends _i1.SmartFake implements _i2.VideoView { + _FakeVideoView_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_16 extends _i1.SmartFake implements _i3.Offset { + _FakeOffset_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSize_17 extends _i1.SmartFake implements _i3.Size { + _FakeSize_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeExpensiveAndroidViewController_18 extends _i1.SmartFake + implements _i4.ExpensiveAndroidViewController { + _FakeExpensiveAndroidViewController_18( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSurfaceAndroidViewController_19 extends _i1.SmartFake + implements _i4.SurfaceAndroidViewController { + _FakeSurfaceAndroidViewController_19( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AdDisplayContainer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdDisplayContainer extends _i1.Mock + implements _i2.AdDisplayContainer { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdDisplayContainer pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdDisplayContainer_1( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdDisplayContainer_1( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdDisplayContainer); +} + +/// A class which mocks [AdError]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdError extends _i1.Mock implements _i2.AdError { + @override + _i2.AdErrorCode get errorCode => (super.noSuchMethod( + Invocation.getter(#errorCode), + returnValue: _i2.AdErrorCode.adsPlayerWasNotProvided, + returnValueForMissingStub: _i2.AdErrorCode.adsPlayerWasNotProvided, + ) as _i2.AdErrorCode); + + @override + int get errorCodeNumber => (super.noSuchMethod( + Invocation.getter(#errorCodeNumber), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + _i2.AdErrorType get errorType => (super.noSuchMethod( + Invocation.getter(#errorType), + returnValue: _i2.AdErrorType.load, + returnValueForMissingStub: _i2.AdErrorType.load, + ) as _i2.AdErrorType); + + @override + String get message => (super.noSuchMethod( + Invocation.getter(#message), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#message), + ), + returnValueForMissingStub: _i5.dummyValue( + this, + Invocation.getter(#message), + ), + ) as String); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdError pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdError_2( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdError_2( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdError); +} + +/// A class which mocks [AdErrorEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdErrorEvent extends _i1.Mock implements _i2.AdErrorEvent { + @override + _i2.AdError get error => (super.noSuchMethod( + Invocation.getter(#error), + returnValue: _FakeAdError_2( + this, + Invocation.getter(#error), + ), + returnValueForMissingStub: _FakeAdError_2( + this, + Invocation.getter(#error), + ), + ) as _i2.AdError); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdErrorEvent pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdErrorEvent_3( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdErrorEvent_3( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdErrorEvent); +} + +/// A class which mocks [AdErrorListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdErrorListener extends _i1.Mock implements _i2.AdErrorListener { + @override + void Function( + _i2.AdErrorListener, + _i2.AdErrorEvent, + ) get onAdError => (super.noSuchMethod( + Invocation.getter(#onAdError), + returnValue: ( + _i2.AdErrorListener pigeon_instance, + _i2.AdErrorEvent event, + ) {}, + returnValueForMissingStub: ( + _i2.AdErrorListener pigeon_instance, + _i2.AdErrorEvent event, + ) {}, + ) as void Function( + _i2.AdErrorListener, + _i2.AdErrorEvent, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdErrorListener pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdErrorListener_4( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdErrorListener_4( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdErrorListener); +} + +/// A class which mocks [AdsLoadedListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsLoadedListener extends _i1.Mock implements _i2.AdsLoadedListener { + @override + void Function( + _i2.AdsLoadedListener, + _i2.AdsManagerLoadedEvent, + ) get onAdsManagerLoaded => (super.noSuchMethod( + Invocation.getter(#onAdsManagerLoaded), + returnValue: ( + _i2.AdsLoadedListener pigeon_instance, + _i2.AdsManagerLoadedEvent event, + ) {}, + returnValueForMissingStub: ( + _i2.AdsLoadedListener pigeon_instance, + _i2.AdsManagerLoadedEvent event, + ) {}, + ) as void Function( + _i2.AdsLoadedListener, + _i2.AdsManagerLoadedEvent, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdsLoadedListener pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdsLoadedListener_5( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdsLoadedListener_5( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdsLoadedListener); +} + +/// A class which mocks [AdsManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsManager extends _i1.Mock implements _i2.AdsManager { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future discardAdBreak() => (super.noSuchMethod( + Invocation.method( + #discardAdBreak, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future pause() => (super.noSuchMethod( + Invocation.method( + #pause, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.AdsManager pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdsManager_6( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdsManager_6( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdsManager); + + @override + _i6.Future addAdErrorListener(_i2.AdErrorListener? errorListener) => + (super.noSuchMethod( + Invocation.method( + #addAdErrorListener, + [errorListener], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future addAdEventListener(_i2.AdEventListener? adEventListener) => + (super.noSuchMethod( + Invocation.method( + #addAdEventListener, + [adEventListener], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future destroy() => (super.noSuchMethod( + Invocation.method( + #destroy, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [AdsManagerLoadedEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsManagerLoadedEvent extends _i1.Mock + implements _i2.AdsManagerLoadedEvent { + @override + _i2.AdsManager get manager => (super.noSuchMethod( + Invocation.getter(#manager), + returnValue: _FakeAdsManager_6( + this, + Invocation.getter(#manager), + ), + returnValueForMissingStub: _FakeAdsManager_6( + this, + Invocation.getter(#manager), + ), + ) as _i2.AdsManager); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdsManagerLoadedEvent pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdsManagerLoadedEvent_7( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdsManagerLoadedEvent_7( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdsManagerLoadedEvent); +} + +/// A class which mocks [AdsLoader]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsLoader extends _i1.Mock implements _i2.AdsLoader { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future addAdErrorListener(_i2.AdErrorListener? listener) => + (super.noSuchMethod( + Invocation.method( + #addAdErrorListener, + [listener], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future addAdsLoadedListener(_i2.AdsLoadedListener? listener) => + (super.noSuchMethod( + Invocation.method( + #addAdsLoadedListener, + [listener], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future requestAds(_i2.AdsRequest? request) => (super.noSuchMethod( + Invocation.method( + #requestAds, + [request], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.AdsLoader pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdsLoader_8( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdsLoader_8( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdsLoader); +} + +/// A class which mocks [AdsRequest]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsRequest extends _i1.Mock implements _i2.AdsRequest { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future setAdTagUrl(String? adTagUrl) => (super.noSuchMethod( + Invocation.method( + #setAdTagUrl, + [adTagUrl], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setContentProgressProvider( + _i2.ContentProgressProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #setContentProgressProvider, + [provider], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.AdsRequest pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdsRequest_9( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdsRequest_9( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdsRequest); +} + +/// A class which mocks [FrameLayout]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFrameLayout extends _i1.Mock implements _i2.FrameLayout { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.FrameLayout pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeFrameLayout_10( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeFrameLayout_10( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.FrameLayout); + + @override + _i6.Future addView(_i2.View? view) => (super.noSuchMethod( + Invocation.method( + #addView, + [view], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [ImaSdkFactory]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockImaSdkFactory extends _i1.Mock implements _i2.ImaSdkFactory { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future<_i2.ImaSdkSettings> createImaSdkSettings() => (super.noSuchMethod( + Invocation.method( + #createImaSdkSettings, + [], + ), + returnValue: + _i6.Future<_i2.ImaSdkSettings>.value(_FakeImaSdkSettings_11( + this, + Invocation.method( + #createImaSdkSettings, + [], + ), + )), + returnValueForMissingStub: + _i6.Future<_i2.ImaSdkSettings>.value(_FakeImaSdkSettings_11( + this, + Invocation.method( + #createImaSdkSettings, + [], + ), + )), + ) as _i6.Future<_i2.ImaSdkSettings>); + + @override + _i6.Future<_i2.AdsLoader> createAdsLoader( + _i2.ImaSdkSettings? settings, + _i2.AdDisplayContainer? container, + ) => + (super.noSuchMethod( + Invocation.method( + #createAdsLoader, + [ + settings, + container, + ], + ), + returnValue: _i6.Future<_i2.AdsLoader>.value(_FakeAdsLoader_8( + this, + Invocation.method( + #createAdsLoader, + [ + settings, + container, + ], + ), + )), + returnValueForMissingStub: + _i6.Future<_i2.AdsLoader>.value(_FakeAdsLoader_8( + this, + Invocation.method( + #createAdsLoader, + [ + settings, + container, + ], + ), + )), + ) as _i6.Future<_i2.AdsLoader>); + + @override + _i6.Future<_i2.AdsRequest> createAdsRequest() => (super.noSuchMethod( + Invocation.method( + #createAdsRequest, + [], + ), + returnValue: _i6.Future<_i2.AdsRequest>.value(_FakeAdsRequest_9( + this, + Invocation.method( + #createAdsRequest, + [], + ), + )), + returnValueForMissingStub: + _i6.Future<_i2.AdsRequest>.value(_FakeAdsRequest_9( + this, + Invocation.method( + #createAdsRequest, + [], + ), + )), + ) as _i6.Future<_i2.AdsRequest>); + + @override + _i2.ImaSdkFactory pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeImaSdkFactory_12( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeImaSdkFactory_12( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.ImaSdkFactory); +} + +/// A class which mocks [ImaSdkSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockImaSdkSettings extends _i1.Mock implements _i2.ImaSdkSettings { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.ImaSdkSettings pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeImaSdkSettings_11( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeImaSdkSettings_11( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.ImaSdkSettings); +} + +/// A class which mocks [VideoAdPlayer]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoAdPlayer extends _i1.Mock implements _i2.VideoAdPlayer { + @override + void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + ) get addCallback => (super.noSuchMethod( + Invocation.getter(#addCallback), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + _i2.AdPodInfo, + ) get loadAd => (super.noSuchMethod( + Invocation.getter(#loadAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + _i2.AdPodInfo adPodInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + _i2.AdPodInfo adPodInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + _i2.AdPodInfo, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + ) get pauseAd => (super.noSuchMethod( + Invocation.getter(#pauseAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + ) get playAd => (super.noSuchMethod( + Invocation.getter(#playAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + )); + + @override + void Function(_i2.VideoAdPlayer) get release => (super.noSuchMethod( + Invocation.getter(#release), + returnValue: (_i2.VideoAdPlayer pigeon_instance) {}, + returnValueForMissingStub: (_i2.VideoAdPlayer pigeon_instance) {}, + ) as void Function(_i2.VideoAdPlayer)); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + ) get removeCallback => (super.noSuchMethod( + Invocation.getter(#removeCallback), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.VideoAdPlayerCallback callback, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.VideoAdPlayerCallback, + )); + + @override + void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + ) get stopAd => (super.noSuchMethod( + Invocation.getter(#stopAd), + returnValue: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + returnValueForMissingStub: ( + _i2.VideoAdPlayer pigeon_instance, + _i2.AdMediaInfo adMediaInfo, + ) {}, + ) as void Function( + _i2.VideoAdPlayer, + _i2.AdMediaInfo, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future setVolume(int? value) => (super.noSuchMethod( + Invocation.method( + #setVolume, + [value], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future setAdProgress(_i2.VideoProgressUpdate? progress) => + (super.noSuchMethod( + Invocation.method( + #setAdProgress, + [progress], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.VideoAdPlayer pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoAdPlayer_13( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoAdPlayer_13( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoAdPlayer); +} + +/// A class which mocks [VideoAdPlayerCallback]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoAdPlayerCallback extends _i1.Mock + implements _i2.VideoAdPlayerCallback { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future onAdProgress( + _i2.AdMediaInfo? adMediaInfo, + _i2.VideoProgressUpdate? videoProgressUpdate, + ) => + (super.noSuchMethod( + Invocation.method( + #onAdProgress, + [ + adMediaInfo, + videoProgressUpdate, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onBuffering(_i2.AdMediaInfo? adMediaInfo) => + (super.noSuchMethod( + Invocation.method( + #onBuffering, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onContentComplete() => (super.noSuchMethod( + Invocation.method( + #onContentComplete, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onEnded(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onEnded, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onError(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onError, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onLoaded(_i2.AdMediaInfo? adMediaInfo) => + (super.noSuchMethod( + Invocation.method( + #onLoaded, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onPause(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onPause, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onPlay(_i2.AdMediaInfo? adMediaInfo) => (super.noSuchMethod( + Invocation.method( + #onPlay, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onResume(_i2.AdMediaInfo? adMediaInfo) => + (super.noSuchMethod( + Invocation.method( + #onResume, + [adMediaInfo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future onVolumeChanged( + _i2.AdMediaInfo? adMediaInfo, + int? percentage, + ) => + (super.noSuchMethod( + Invocation.method( + #onVolumeChanged, + [ + adMediaInfo, + percentage, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i2.VideoAdPlayerCallback pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoAdPlayerCallback_14( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoAdPlayerCallback_14( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoAdPlayerCallback); +} + +/// A class which mocks [VideoView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoView extends _i1.Mock implements _i2.VideoView { + @override + void Function( + _i2.VideoView, + _i2.MediaPlayer, + int, + int, + ) get onError => (super.noSuchMethod( + Invocation.getter(#onError), + returnValue: ( + _i2.VideoView pigeon_instance, + _i2.MediaPlayer player, + int what, + int extra, + ) {}, + returnValueForMissingStub: ( + _i2.VideoView pigeon_instance, + _i2.MediaPlayer player, + int what, + int extra, + ) {}, + ) as void Function( + _i2.VideoView, + _i2.MediaPlayer, + int, + int, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i6.Future setVideoUri(String? uri) => (super.noSuchMethod( + Invocation.method( + #setVideoUri, + [uri], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future getCurrentPosition() => (super.noSuchMethod( + Invocation.method( + #getCurrentPosition, + [], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + + @override + _i2.VideoView pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeVideoView_15( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeVideoView_15( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.VideoView); +} + +/// A class which mocks [SurfaceAndroidViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSurfaceAndroidViewController extends _i1.Mock + implements _i4.SurfaceAndroidViewController { + @override + bool get requiresViewComposition => (super.noSuchMethod( + Invocation.getter(#requiresViewComposition), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + int get viewId => (super.noSuchMethod( + Invocation.getter(#viewId), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + bool get awaitingCreation => (super.noSuchMethod( + Invocation.getter(#awaitingCreation), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i4.PointTransformer get pointTransformer => (super.noSuchMethod( + Invocation.getter(#pointTransformer), + returnValue: (_i3.Offset position) => _FakeOffset_16( + this, + Invocation.getter(#pointTransformer), + ), + returnValueForMissingStub: (_i3.Offset position) => _FakeOffset_16( + this, + Invocation.getter(#pointTransformer), + ), + ) as _i4.PointTransformer); + + @override + set pointTransformer(_i4.PointTransformer? transformer) => super.noSuchMethod( + Invocation.setter( + #pointTransformer, + transformer, + ), + returnValueForMissingStub: null, + ); + + @override + bool get isCreated => (super.noSuchMethod( + Invocation.getter(#isCreated), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + List<_i4.PlatformViewCreatedCallback> get createdCallbacks => + (super.noSuchMethod( + Invocation.getter(#createdCallbacks), + returnValue: <_i4.PlatformViewCreatedCallback>[], + returnValueForMissingStub: <_i4.PlatformViewCreatedCallback>[], + ) as List<_i4.PlatformViewCreatedCallback>); + + @override + _i6.Future setOffset(_i3.Offset? off) => (super.noSuchMethod( + Invocation.method( + #setOffset, + [off], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future create({ + _i3.Size? size, + _i3.Offset? position, + }) => + (super.noSuchMethod( + Invocation.method( + #create, + [], + { + #size: size, + #position: position, + }, + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future<_i3.Size> setSize(_i3.Size? size) => (super.noSuchMethod( + Invocation.method( + #setSize, + [size], + ), + returnValue: _i6.Future<_i3.Size>.value(_FakeSize_17( + this, + Invocation.method( + #setSize, + [size], + ), + )), + returnValueForMissingStub: _i6.Future<_i3.Size>.value(_FakeSize_17( + this, + Invocation.method( + #setSize, + [size], + ), + )), + ) as _i6.Future<_i3.Size>); + + @override + _i6.Future sendMotionEvent(_i4.AndroidMotionEvent? event) => + (super.noSuchMethod( + Invocation.method( + #sendMotionEvent, + [event], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + void addOnPlatformViewCreatedListener( + _i4.PlatformViewCreatedCallback? listener) => + super.noSuchMethod( + Invocation.method( + #addOnPlatformViewCreatedListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeOnPlatformViewCreatedListener( + _i4.PlatformViewCreatedCallback? listener) => + super.noSuchMethod( + Invocation.method( + #removeOnPlatformViewCreatedListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + _i6.Future setLayoutDirection(_i3.TextDirection? layoutDirection) => + (super.noSuchMethod( + Invocation.method( + #setLayoutDirection, + [layoutDirection], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future dispatchPointerEvent(_i4.PointerEvent? event) => + (super.noSuchMethod( + Invocation.method( + #dispatchPointerEvent, + [event], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future clearFocus() => (super.noSuchMethod( + Invocation.method( + #clearFocus, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future dispose() => (super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [PlatformViewsServiceProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockPlatformViewsServiceProxy extends _i1.Mock + implements _i7.PlatformViewsServiceProxy { + @override + _i4.ExpensiveAndroidViewController initExpensiveAndroidView({ + required int? id, + required String? viewType, + required _i3.TextDirection? layoutDirection, + dynamic creationParams, + _i4.MessageCodec? creationParamsCodec, + _i3.VoidCallback? onFocus, + }) => + (super.noSuchMethod( + Invocation.method( + #initExpensiveAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + returnValue: _FakeExpensiveAndroidViewController_18( + this, + Invocation.method( + #initExpensiveAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + returnValueForMissingStub: _FakeExpensiveAndroidViewController_18( + this, + Invocation.method( + #initExpensiveAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + ) as _i4.ExpensiveAndroidViewController); + + @override + _i4.SurfaceAndroidViewController initSurfaceAndroidView({ + required int? id, + required String? viewType, + required _i3.TextDirection? layoutDirection, + dynamic creationParams, + _i4.MessageCodec? creationParamsCodec, + _i3.VoidCallback? onFocus, + }) => + (super.noSuchMethod( + Invocation.method( + #initSurfaceAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + returnValue: _FakeSurfaceAndroidViewController_19( + this, + Invocation.method( + #initSurfaceAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + returnValueForMissingStub: _FakeSurfaceAndroidViewController_19( + this, + Invocation.method( + #initSurfaceAndroidView, + [], + { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }, + ), + ), + ) as _i4.SurfaceAndroidViewController); +} diff --git a/packages/interactive_media_ads/test/android/ads_manager_tests.dart b/packages/interactive_media_ads/test/android/ads_manager_tests.dart new file mode 100644 index 00000000000..b05811102ca --- /dev/null +++ b/packages/interactive_media_ads/test/android/ads_manager_tests.dart @@ -0,0 +1,136 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:interactive_media_ads/src/android/android_ads_manager.dart'; +import 'package:interactive_media_ads/src/android/android_ads_manager_delegate.dart'; +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as ima; +import 'package:interactive_media_ads/src/android/interactive_media_ads_proxy.dart'; +import 'package:interactive_media_ads/src/platform_interface/platform_interface.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'ads_manager_tests.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + group('AndroidAdsManager', () { + test('destroy', () { + final MockAdsManager mockAdsManager = MockAdsManager(); + final AndroidAdsManager adsManager = AndroidAdsManager(mockAdsManager); + adsManager.destroy(); + + verify(mockAdsManager.destroy()); + }); + + test('init', () { + final MockAdsManager mockAdsManager = MockAdsManager(); + final AndroidAdsManager adsManager = AndroidAdsManager(mockAdsManager); + adsManager.init(AdsManagerInitParams()); + + verify(mockAdsManager.init()); + }); + + test('start', () { + final MockAdsManager mockAdsManager = MockAdsManager(); + final AndroidAdsManager adsManager = AndroidAdsManager(mockAdsManager); + adsManager.start(AdsManagerStartParams()); + + verify(mockAdsManager.start()); + }); + + test('onAdEvent', () async { + final MockAdsManager mockAdsManager = MockAdsManager(); + + late final void Function( + ima.AdEventListener, + ima.AdEvent, + ) onAdEventCallback; + + final InteractiveMediaAdsProxy proxy = InteractiveMediaAdsProxy( + newAdEventListener: ({ + required void Function( + ima.AdEventListener, + ima.AdEvent, + ) onAdEvent, + }) { + onAdEventCallback = onAdEvent; + return MockAdEventListener(); + }, + newAdErrorListener: ({required dynamic onAdError}) { + return MockAdErrorListener(); + }, + ); + + final AndroidAdsManager adsManager = AndroidAdsManager( + mockAdsManager, + proxy: proxy, + ); + await adsManager.setAdsManagerDelegate( + AndroidAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams( + onAdEvent: expectAsync1((_) {}), + ), + ), + ); + + final MockAdEvent mockAdEvent = MockAdEvent(); + when(mockAdEvent.type).thenReturn(ima.AdEventType.allAdsCompleted); + onAdEventCallback(MockAdEventListener(), mockAdEvent); + }); + + test('onAdErrorEvent', () async { + final MockAdsManager mockAdsManager = MockAdsManager(); + + late final void Function( + ima.AdErrorListener, + ima.AdErrorEvent, + ) onAdErrorCallback; + + final InteractiveMediaAdsProxy proxy = InteractiveMediaAdsProxy( + newAdEventListener: ({required dynamic onAdEvent}) { + return MockAdEventListener(); + }, + newAdErrorListener: ({ + required void Function( + ima.AdErrorListener, + ima.AdErrorEvent, + ) onAdError, + }) { + onAdErrorCallback = onAdError; + return MockAdErrorListener(); + }, + ); + + final AndroidAdsManager adsManager = AndroidAdsManager( + mockAdsManager, + proxy: proxy, + ); + await adsManager.setAdsManagerDelegate( + AndroidAdsManagerDelegate( + PlatformAdsManagerDelegateCreationParams( + onAdErrorEvent: expectAsync1((_) {}), + ), + ), + ); + + final MockAdErrorEvent mockErrorEvent = MockAdErrorEvent(); + final MockAdError mockError = MockAdError(); + when(mockError.errorType).thenReturn(ima.AdErrorType.load); + when(mockError.errorCode) + .thenReturn(ima.AdErrorCode.adsRequestNetworkError); + when(mockError.message).thenReturn('error message'); + when(mockErrorEvent.error).thenReturn(mockError); + onAdErrorCallback(MockAdErrorListener(), mockErrorEvent); + }); + }); +} diff --git a/packages/interactive_media_ads/test/android/ads_manager_tests.mocks.dart b/packages/interactive_media_ads/test/android/ads_manager_tests.mocks.dart new file mode 100644 index 00000000000..f86c6778845 --- /dev/null +++ b/packages/interactive_media_ads/test/android/ads_manager_tests.mocks.dart @@ -0,0 +1,501 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in interactive_media_ads/test/android/ads_manager_tests.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:interactive_media_ads/src/android/interactive_media_ads.g.dart' + as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdError_1 extends _i1.SmartFake implements _i2.AdError { + _FakeAdError_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdErrorEvent_2 extends _i1.SmartFake implements _i2.AdErrorEvent { + _FakeAdErrorEvent_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdErrorListener_3 extends _i1.SmartFake + implements _i2.AdErrorListener { + _FakeAdErrorListener_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdEvent_4 extends _i1.SmartFake implements _i2.AdEvent { + _FakeAdEvent_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdEventListener_5 extends _i1.SmartFake + implements _i2.AdEventListener { + _FakeAdEventListener_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAdsManager_6 extends _i1.SmartFake implements _i2.AdsManager { + _FakeAdsManager_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AdError]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdError extends _i1.Mock implements _i2.AdError { + @override + _i2.AdErrorCode get errorCode => (super.noSuchMethod( + Invocation.getter(#errorCode), + returnValue: _i2.AdErrorCode.adsPlayerWasNotProvided, + returnValueForMissingStub: _i2.AdErrorCode.adsPlayerWasNotProvided, + ) as _i2.AdErrorCode); + + @override + int get errorCodeNumber => (super.noSuchMethod( + Invocation.getter(#errorCodeNumber), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + _i2.AdErrorType get errorType => (super.noSuchMethod( + Invocation.getter(#errorType), + returnValue: _i2.AdErrorType.load, + returnValueForMissingStub: _i2.AdErrorType.load, + ) as _i2.AdErrorType); + + @override + String get message => (super.noSuchMethod( + Invocation.getter(#message), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#message), + ), + returnValueForMissingStub: _i3.dummyValue( + this, + Invocation.getter(#message), + ), + ) as String); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdError pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdError_1( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdError_1( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdError); +} + +/// A class which mocks [AdErrorEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdErrorEvent extends _i1.Mock implements _i2.AdErrorEvent { + @override + _i2.AdError get error => (super.noSuchMethod( + Invocation.getter(#error), + returnValue: _FakeAdError_1( + this, + Invocation.getter(#error), + ), + returnValueForMissingStub: _FakeAdError_1( + this, + Invocation.getter(#error), + ), + ) as _i2.AdError); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdErrorEvent pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdErrorEvent_2( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdErrorEvent_2( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdErrorEvent); +} + +/// A class which mocks [AdErrorListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdErrorListener extends _i1.Mock implements _i2.AdErrorListener { + @override + void Function( + _i2.AdErrorListener, + _i2.AdErrorEvent, + ) get onAdError => (super.noSuchMethod( + Invocation.getter(#onAdError), + returnValue: ( + _i2.AdErrorListener pigeon_instance, + _i2.AdErrorEvent event, + ) {}, + returnValueForMissingStub: ( + _i2.AdErrorListener pigeon_instance, + _i2.AdErrorEvent event, + ) {}, + ) as void Function( + _i2.AdErrorListener, + _i2.AdErrorEvent, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdErrorListener pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdErrorListener_3( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdErrorListener_3( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdErrorListener); +} + +/// A class which mocks [AdEvent]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdEvent extends _i1.Mock implements _i2.AdEvent { + @override + _i2.AdEventType get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: _i2.AdEventType.adBreakEnded, + returnValueForMissingStub: _i2.AdEventType.adBreakEnded, + ) as _i2.AdEventType); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdEvent pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdEvent_4( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdEvent_4( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdEvent); +} + +/// A class which mocks [AdEventListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdEventListener extends _i1.Mock implements _i2.AdEventListener { + @override + void Function( + _i2.AdEventListener, + _i2.AdEvent, + ) get onAdEvent => (super.noSuchMethod( + Invocation.getter(#onAdEvent), + returnValue: ( + _i2.AdEventListener pigeon_instance, + _i2.AdEvent event, + ) {}, + returnValueForMissingStub: ( + _i2.AdEventListener pigeon_instance, + _i2.AdEvent event, + ) {}, + ) as void Function( + _i2.AdEventListener, + _i2.AdEvent, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i2.AdEventListener pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdEventListener_5( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdEventListener_5( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdEventListener); +} + +/// A class which mocks [AdsManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAdsManager extends _i1.Mock implements _i2.AdsManager { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) as _i2.PigeonInstanceManager); + + @override + _i4.Future discardAdBreak() => (super.noSuchMethod( + Invocation.method( + #discardAdBreak, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future pause() => (super.noSuchMethod( + Invocation.method( + #pause, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future start() => (super.noSuchMethod( + Invocation.method( + #start, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i2.AdsManager pigeon_copy() => (super.noSuchMethod( + Invocation.method( + #pigeon_copy, + [], + ), + returnValue: _FakeAdsManager_6( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + returnValueForMissingStub: _FakeAdsManager_6( + this, + Invocation.method( + #pigeon_copy, + [], + ), + ), + ) as _i2.AdsManager); + + @override + _i4.Future addAdErrorListener(_i2.AdErrorListener? errorListener) => + (super.noSuchMethod( + Invocation.method( + #addAdErrorListener, + [errorListener], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future addAdEventListener(_i2.AdEventListener? adEventListener) => + (super.noSuchMethod( + Invocation.method( + #addAdEventListener, + [adEventListener], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future destroy() => (super.noSuchMethod( + Invocation.method( + #destroy, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future init() => (super.noSuchMethod( + Invocation.method( + #init, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} diff --git a/packages/interactive_media_ads/test/version_test.dart b/packages/interactive_media_ads/test/version_test.dart new file mode 100644 index 00000000000..0bd444e995b --- /dev/null +++ b/packages/interactive_media_ads/test/version_test.dart @@ -0,0 +1,27 @@ +// 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:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('AdsRequestProxyApi.pluginVersion matches pubspec version', () { + final String pubspecPath = '${Directory.current.path}/pubspec.yaml'; + final String pubspec = File(pubspecPath).readAsStringSync(); + final RegExp regex = RegExp(r'version:\s*(.*?) #'); + final RegExpMatch? match = regex.firstMatch(pubspec); + final String pubspecVersion = match!.group(1)!.trim(); + + final String adsRequestProxyApiPath = + '${Directory.current.path}/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt'; + final String apiFileAsString = + File(adsRequestProxyApiPath).readAsStringSync(); + + expect( + apiFileAsString, + contains('const val pluginVersion = "$pubspecVersion"'), + ); + }); +} diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index 5b4c76c6098..e85c3676990 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.2.4 * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. +* Adds Swift Package Manager compatibility. ## 0.2.3+2 diff --git a/packages/ios_platform_images/example/ios/Runner/AppDelegate.swift b/packages/ios_platform_images/example/ios/Runner/AppDelegate.swift index d83c0ff0bee..4580c8e76da 100644 --- a/packages/ios_platform_images/example/ios/Runner/AppDelegate.swift +++ b/packages/ios_platform_images/example/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/ios_platform_images/example/ios/RunnerTests/UIImage+ios_platform_imagesTests.m b/packages/ios_platform_images/example/ios/RunnerTests/UIImage+ios_platform_imagesTests.m index d107ac4bd34..4e176e84a5c 100644 --- a/packages/ios_platform_images/example/ios/RunnerTests/UIImage+ios_platform_imagesTests.m +++ b/packages/ios_platform_images/example/ios/RunnerTests/UIImage+ios_platform_imagesTests.m @@ -21,7 +21,7 @@ - (void)testMultiResolutionImageUsesBest { const double height2x = 250; // The height of assets/2.0x/multisize.png. // Loading assets should get the best available asset for the screen scale when resolution-aware // assets are available (and the example app has 1x and 2x for this asset). See - // https://docs.flutter.dev/ui/assets/assets-and-images#resolution-aware + // https://flutter.dev/to/resolution-aware-images if (UIScreen.mainScreen.scale > 1.0) { XCTAssertEqualWithAccuracy(image.size.height, height2x, 0.00001); } else { diff --git a/packages/ios_platform_images/ios/ios_platform_images.podspec b/packages/ios_platform_images/ios/ios_platform_images.podspec index bb9574e4260..cbe750ced4a 100644 --- a/packages/ios_platform_images/ios/ios_platform_images.podspec +++ b/packages/ios_platform_images/ios/ios_platform_images.podspec @@ -16,6 +16,7 @@ Downloaded by pub (not CocoaPods). s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/ios_platform_images' } s.documentation_url = 'https://pub.dev/packages/ios_platform_images' s.source_files = 'Classes/**/*.swift' + s.source_files = 'ios_platform_images/Sources/ios_platform_images/**/*.swift' s.dependency 'Flutter' s.platform = :ios, '12.0' @@ -25,5 +26,5 @@ Downloaded by pub (not CocoaPods). 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } s.swift_version = '5.0' - s.resource_bundles = {'ios_platform_images_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'ios_platform_images_privacy' => ['ios_platform_images/Sources/ios_platform_images/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/ios_platform_images/ios/ios_platform_images/Package.swift b/packages/ios_platform_images/ios/ios_platform_images/Package.swift new file mode 100644 index 00000000000..b594a5a8d60 --- /dev/null +++ b/packages/ios_platform_images/ios/ios_platform_images/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "ios_platform_images", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "ios-platform-images", targets: ["ios_platform_images"]) + ], + dependencies: [], + targets: [ + .target( + name: "ios_platform_images", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.swift b/packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/IosPlatformImagesPlugin.swift similarity index 100% rename from packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.swift rename to packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/IosPlatformImagesPlugin.swift diff --git a/packages/ios_platform_images/ios/Resources/PrivacyInfo.xcprivacy b/packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/ios_platform_images/ios/Resources/PrivacyInfo.xcprivacy rename to packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/Resources/PrivacyInfo.xcprivacy diff --git a/packages/ios_platform_images/ios/Classes/UIImageIosPlatformImages.swift b/packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/UIImageIosPlatformImages.swift similarity index 95% rename from packages/ios_platform_images/ios/Classes/UIImageIosPlatformImages.swift rename to packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/UIImageIosPlatformImages.swift index 2e9cdefe673..af7f3497222 100644 --- a/packages/ios_platform_images/ios/Classes/UIImageIosPlatformImages.swift +++ b/packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/UIImageIosPlatformImages.swift @@ -19,7 +19,7 @@ import UIKit /// `[UIImage flutterImageWithName:@"assets/foo.png"]` will load /// "assets/2.0x/foo.png". /// - /// See also https://flutter.dev/docs/development/ui/assets-and-images + /// See also https://docs.flutter.dev/ui/assets/assets-and-images /// /// Note: We don't yet support images from package dependencies (ex. /// `AssetImage('icons/heart.png', package: 'my_icons')`). diff --git a/packages/ios_platform_images/ios/Classes/messages.g.swift b/packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/messages.g.swift similarity index 100% rename from packages/ios_platform_images/ios/Classes/messages.g.swift rename to packages/ios_platform_images/ios/ios_platform_images/Sources/ios_platform_images/messages.g.swift diff --git a/packages/ios_platform_images/pigeons/messages.dart b/packages/ios_platform_images/pigeons/messages.dart index 36cd9931e5b..f45a914e19a 100644 --- a/packages/ios_platform_images/pigeons/messages.dart +++ b/packages/ios_platform_images/pigeons/messages.dart @@ -6,7 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - swiftOut: 'ios/Classes/messages.g.swift', + swiftOut: + 'ios/ios_platform_images/Sources/ios_platform_messages/messages.g.swift', copyrightHeader: 'pigeons/copyright.txt', )) diff --git a/packages/ios_platform_images/pubspec.yaml b/packages/ios_platform_images/pubspec.yaml index 7500447d915..e17ed82f26c 100644 --- a/packages/ios_platform_images/pubspec.yaml +++ b/packages/ios_platform_images/pubspec.yaml @@ -2,7 +2,7 @@ name: ios_platform_images description: A plugin to share images between Flutter and iOS in add-to-app setups. repository: https://github.com/flutter/packages/tree/main/packages/ios_platform_images issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+ios_platform_images%22 -version: 0.2.3+2 +version: 0.2.4 environment: sdk: ^3.2.3 diff --git a/packages/local_auth/local_auth/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/local_auth/local_auth/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/local_auth/local_auth/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/local_auth/local_auth/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/local_auth/local_auth/example/android/build.gradle b/packages/local_auth/local_auth/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/local_auth/local_auth/example/android/build.gradle +++ b/packages/local_auth/local_auth/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/local_auth/local_auth/example/android/settings.gradle b/packages/local_auth/local_auth/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/local_auth/local_auth/example/android/settings.gradle +++ b/packages/local_auth/local_auth/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index b6441c94e12..435e2ed98ab 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,3 +1,16 @@ +## 1.0.41 + +* Updates espresso to 3.6.1. + +## 1.0.40 + +* Updates androidx.core version to 1.13.1. + +## 1.0.39 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 1.0.38 * Updates minSdkVersion to 19. diff --git a/packages/local_auth/local_auth_android/README.md b/packages/local_auth/local_auth_android/README.md index 047e82ef64b..d9e3b8bba79 100644 --- a/packages/local_auth/local_auth_android/README.md +++ b/packages/local_auth/local_auth_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/local_auth -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle index ed0d7ad25e3..57059c3dc56 100644 --- a/packages/local_auth/local_auth_android/android/build.gradle +++ b/packages/local_auth/local_auth_android/android/build.gradle @@ -59,7 +59,7 @@ android { } dependencies { - api "androidx.core:core:1.10.1" + api "androidx.core:core:1.13.1" api "androidx.biometric:biometric:1.1.0" api "androidx.fragment:fragment:1.6.2" testImplementation 'junit:junit:4.13.2' @@ -67,7 +67,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.10.3' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' // TODO(camsim99): org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. // This should be removed when https://github.com/flutter/flutter/issues/125062 is fixed. implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10")) diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 7ed2c04a8f6..5d1fe195449 100644 --- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -67,22 +67,6 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { } }; - /** - * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. - * - *

Calling this will register the plugin with the passed registrar. However, plugins - * initialized this way won't react to changes in activity or context. - * - * @param registrar provides access to necessary plugin context. - */ - @SuppressWarnings("deprecation") - public static void registerWith(@NonNull PluginRegistry.Registrar registrar) { - final LocalAuthPlugin plugin = new LocalAuthPlugin(); - plugin.activity = registrar.activity(); - LocalAuthApi.setup(registrar.messenger(), plugin); - registrar.addActivityResultListener(plugin.resultListener); - } - /** * Default constructor for LocalAuthPlugin. * diff --git a/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/local_auth/local_auth_android/example/android/build.gradle b/packages/local_auth/local_auth_android/example/android/build.gradle index da0dec5c327..1019f64db81 100644 --- a/packages/local_auth/local_auth_android/example/android/build.gradle +++ b/packages/local_auth/local_auth_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/local_auth/local_auth_android/example/android/settings.gradle b/packages/local_auth/local_auth_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/local_auth/local_auth_android/example/android/settings.gradle +++ b/packages/local_auth/local_auth_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/local_auth/local_auth_android/example/pubspec.yaml b/packages/local_auth/local_auth_android/example/pubspec.yaml index 4ef04f6f78d..36b392ed098 100644 --- a/packages/local_auth/local_auth_android/example/pubspec.yaml +++ b/packages/local_auth/local_auth_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the local_auth_android plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_android/lib/local_auth_android.dart b/packages/local_auth/local_auth_android/lib/local_auth_android.dart index 338bba235b2..cf117cb0066 100644 --- a/packages/local_auth/local_auth_android/lib/local_auth_android.dart +++ b/packages/local_auth/local_auth_android/lib/local_auth_android.dart @@ -44,7 +44,7 @@ class LocalAuthAndroid extends LocalAuthPlatform { _pigeonStringsFromAuthMessages(localizedReason, authMessages)); // TODO(stuartmorgan): Replace this with structured errors, coordinated // across all platform implementations, per - // https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#platform-exception-handling + // https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling // The PlatformExceptions thrown here are for compatibiilty with the // previous Java implementation. switch (result) { diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index 5b4b57a74d2..30c542ff3de 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -2,11 +2,11 @@ name: local_auth_android description: Android implementation of the local_auth plugin. repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.38 +version: 1.0.41 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_darwin/CHANGELOG.md b/packages/local_auth/local_auth_darwin/CHANGELOG.md index 2d209e97bef..28d789ca0f6 100644 --- a/packages/local_auth/local_auth_darwin/CHANGELOG.md +++ b/packages/local_auth/local_auth_darwin/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.3.1 + +* Adjusts implementation for improved testability, and removes use of OCMock. + +## 1.3.0 + +* Adds Swift Package Manager compatibility. + ## 1.2.2 * Adds compatibility with `intl` 0.19.0. diff --git a/packages/local_auth/local_auth_darwin/README.md b/packages/local_auth/local_auth_darwin/README.md index 46cea659392..6c930920542 100644 --- a/packages/local_auth/local_auth_darwin/README.md +++ b/packages/local_auth/local_auth_darwin/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/local_auth -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/local_auth/local_auth_darwin/darwin/Assets/.gitkeep b/packages/local_auth/local_auth_darwin/darwin/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h b/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h deleted file mode 100644 index eb12b29fae3..00000000000 --- a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h +++ /dev/null @@ -1,17 +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 - -/// Protocol for a source of LAContext instances. Used to allow context injection in unit tests. -@protocol FLADAuthContextFactory -- (LAContext *)createAuthContext; -@end - -@interface FLALocalAuthPlugin () -/// Returns an instance that uses the given factory to create LAContexts. -- (instancetype)initWithContextFactory:(NSObject *)factory - NS_DESIGNATED_INITIALIZER; -@end diff --git a/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.m b/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.m deleted file mode 100644 index 786316835d7..00000000000 --- a/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.m +++ /dev/null @@ -1,532 +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 LocalAuthentication; -@import XCTest; -@import local_auth_darwin; - -#import - -// Set a long timeout to avoid flake due to slow CI. -static const NSTimeInterval kTimeout = 30.0; - -/** - * A context factory that returns preset contexts. - */ -@interface StubAuthContextFactory : NSObject -@property(copy, nonatomic) NSMutableArray *contexts; -- (instancetype)initWithContexts:(NSArray *)contexts; -@end - -@implementation StubAuthContextFactory - -- (instancetype)initWithContexts:(NSArray *)contexts { - self = [super init]; - if (self) { - _contexts = [contexts mutableCopy]; - } - return self; -} - -- (LAContext *)createAuthContext { - NSAssert(self.contexts.count > 0, @"Insufficient test contexts provided"); - LAContext *context = [self.contexts firstObject]; - [self.contexts removeObjectAtIndex:0]; - return context; -} - -@end - -#pragma mark - - -@interface FLALocalAuthPluginTests : XCTestCase -@end - -@implementation FLALocalAuthPluginTests - -- (void)setUp { - self.continueAfterFailure = NO; -} - -- (void)testSuccessfullAuthWithBiometrics { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - FLADAuthStrings *strings = [self createAuthStrings]; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(YES, nil); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:YES - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - XCTAssertEqual(resultDetails.result, FLADAuthResultSuccess); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testSuccessfullAuthWithoutBiometrics { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; - FLADAuthStrings *strings = [self createAuthStrings]; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(YES, nil); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:NO - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - XCTAssertEqual(resultDetails.result, FLADAuthResultSuccess); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testFailedAuthWithBiometrics { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - FLADAuthStrings *strings = [self createAuthStrings]; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:YES - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - // TODO(stuartmorgan): Fix this; this was the pre-Pigeon-migration - // behavior, so is preserved as part of the migration, but a failed - // authentication should return failure, not an error that results in a - // PlatformException. - XCTAssertEqual(resultDetails.result, FLADAuthResultErrorNotAvailable); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testFailedWithUnknownErrorCode { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; - FLADAuthStrings *strings = [self createAuthStrings]; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:NO - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - XCTAssertEqual(resultDetails.result, FLADAuthResultErrorNotAvailable); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testSystemCancelledWithoutStickyAuth { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; - FLADAuthStrings *strings = [self createAuthStrings]; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(NO, [NSError errorWithDomain:@"error" code:LAErrorSystemCancel userInfo:nil]); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:NO - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - XCTAssertEqual(resultDetails.result, FLADAuthResultFailure); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testFailedAuthWithoutBiometrics { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; - FLADAuthStrings *strings = [self createAuthStrings]; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:NO - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - // TODO(stuartmorgan): Fix this; this was the pre-Pigeon-migration - // behavior, so is preserved as part of the migration, but a failed - // authentication should return failure, not an error that results in a - // PlatformException. - XCTAssertEqual(resultDetails.result, FLADAuthResultErrorNotAvailable); - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testLocalizedFallbackTitle { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; - FLADAuthStrings *strings = [self createAuthStrings]; - strings.localizedFallbackTitle = @"a title"; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(YES, nil); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:NO - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - OCMVerify([mockAuthContext - setLocalizedFallbackTitle:strings.localizedFallbackTitle]); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testSkippedLocalizedFallbackTitle { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; - FLADAuthStrings *strings = [self createAuthStrings]; - strings.localizedFallbackTitle = nil; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not - // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on - // a background thread. - void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { - void (^reply)(BOOL, NSError *); - [invocation getArgument:&reply atIndex:4]; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ - reply(YES, nil); - }); - }; - OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:strings.reason reply:[OCMArg any]]) - .andDo(backgroundThreadReplyCaller); - - XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; - [plugin authenticateWithOptions:[FLADAuthOptions makeWithBiometricOnly:NO - sticky:NO - useErrorDialogs:NO] - strings:strings - completion:^(FLADAuthResultDetails *_Nullable resultDetails, - FlutterError *_Nullable error) { - OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeout handler:nil]; -} - -- (void)testDeviceSupportsBiometrics_withEnrolledHardware { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - - FlutterError *error; - NSNumber *result = [plugin deviceCanSupportBiometricsWithError:&error]; - XCTAssertTrue([result boolValue]); - XCTAssertNil(error); -} - -- (void)testDeviceSupportsBiometrics_withNonEnrolledHardware { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - void (^canEvaluatePolicyHandler)(NSInvocation *) = ^(NSInvocation *invocation) { - // Write error - NSError *__autoreleasing *authError; - [invocation getArgument:&authError atIndex:3]; - *authError = [NSError errorWithDomain:@"error" code:LAErrorBiometryNotEnrolled userInfo:nil]; - // Write return value - BOOL returnValue = NO; - NSValue *nsReturnValue = [NSValue valueWithBytes:&returnValue objCType:@encode(BOOL)]; - [invocation setReturnValue:&nsReturnValue]; - }; - OCMStub([mockAuthContext canEvaluatePolicy:policy - error:(NSError * __autoreleasing *)[OCMArg anyPointer]]) - .andDo(canEvaluatePolicyHandler); - - FlutterError *error; - NSNumber *result = [plugin deviceCanSupportBiometricsWithError:&error]; - XCTAssertTrue([result boolValue]); - XCTAssertNil(error); -} - -- (void)testDeviceSupportsBiometrics_withNoBiometricHardware { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - void (^canEvaluatePolicyHandler)(NSInvocation *) = ^(NSInvocation *invocation) { - // Write error - NSError *__autoreleasing *authError; - [invocation getArgument:&authError atIndex:3]; - *authError = [NSError errorWithDomain:@"error" code:0 userInfo:nil]; - // Write return value - BOOL returnValue = NO; - NSValue *nsReturnValue = [NSValue valueWithBytes:&returnValue objCType:@encode(BOOL)]; - [invocation setReturnValue:&nsReturnValue]; - }; - OCMStub([mockAuthContext canEvaluatePolicy:policy - error:(NSError * __autoreleasing *)[OCMArg anyPointer]]) - .andDo(canEvaluatePolicyHandler); - - FlutterError *error; - NSNumber *result = [plugin deviceCanSupportBiometricsWithError:&error]; - XCTAssertFalse([result boolValue]); - XCTAssertNil(error); -} - -- (void)testGetEnrolledBiometricsWithFaceID { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - OCMStub([mockAuthContext biometryType]).andReturn(LABiometryTypeFaceID); - - FlutterError *error; - NSArray *result = [plugin getEnrolledBiometricsWithError:&error]; - XCTAssertEqual([result count], 1); - XCTAssertEqual(result[0].value, FLADAuthBiometricFace); - XCTAssertNil(error); -} - -- (void)testGetEnrolledBiometricsWithTouchID { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); - OCMStub([mockAuthContext biometryType]).andReturn(LABiometryTypeTouchID); - - FlutterError *error; - NSArray *result = [plugin getEnrolledBiometricsWithError:&error]; - XCTAssertEqual([result count], 1); - XCTAssertEqual(result[0].value, FLADAuthBiometricFingerprint); - XCTAssertNil(error); -} - -- (void)testGetEnrolledBiometricsWithoutEnrolledHardware { - id mockAuthContext = OCMClassMock([LAContext class]); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; - void (^canEvaluatePolicyHandler)(NSInvocation *) = ^(NSInvocation *invocation) { - // Write error - NSError *__autoreleasing *authError; - [invocation getArgument:&authError atIndex:3]; - *authError = [NSError errorWithDomain:@"error" code:LAErrorBiometryNotEnrolled userInfo:nil]; - // Write return value - BOOL returnValue = NO; - NSValue *nsReturnValue = [NSValue valueWithBytes:&returnValue objCType:@encode(BOOL)]; - [invocation setReturnValue:&nsReturnValue]; - }; - OCMStub([mockAuthContext canEvaluatePolicy:policy - error:(NSError * __autoreleasing *)[OCMArg anyPointer]]) - .andDo(canEvaluatePolicyHandler); - - FlutterError *error; - NSArray *result = [plugin getEnrolledBiometricsWithError:&error]; - XCTAssertEqual([result count], 0); - XCTAssertNil(error); -} - -- (void)testIsDeviceSupportedHandlesSupported { - id mockAuthContext = OCMClassMock([LAContext class]); - OCMStub([mockAuthContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication - error:[OCMArg setTo:nil]]) - .andReturn(YES); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - FlutterError *error; - NSNumber *result = [plugin isDeviceSupportedWithError:&error]; - XCTAssertTrue([result boolValue]); - XCTAssertNil(error); -} - -- (void)testIsDeviceSupportedHandlesUnsupported { - id mockAuthContext = OCMClassMock([LAContext class]); - OCMStub([mockAuthContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication - error:[OCMArg setTo:nil]]) - .andReturn(NO); - FLALocalAuthPlugin *plugin = [[FLALocalAuthPlugin alloc] - initWithContextFactory:[[StubAuthContextFactory alloc] - initWithContexts:@[ mockAuthContext ]]]; - - FlutterError *error; - NSNumber *result = [plugin isDeviceSupportedWithError:&error]; - XCTAssertFalse([result boolValue]); - XCTAssertNil(error); -} - -// Creates an FLADAuthStrings with placeholder values. -- (FLADAuthStrings *)createAuthStrings { - return [FLADAuthStrings makeWithReason:@"a reason" - lockOut:@"locked out" - goToSettingsButton:@"Go To Settings" - goToSettingsDescription:@"Settings" - cancelButton:@"Cancel" - localizedFallbackTitle:nil]; -} -@end diff --git a/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift b/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift new file mode 100644 index 00000000000..865be355fd1 --- /dev/null +++ b/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift @@ -0,0 +1,397 @@ +// 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 XCTest + +@testable import local_auth_darwin + +// Set a long timeout to avoid flake due to slow CI. +private let timeout: TimeInterval = 30.0 + +/// A context factory that returns preset contexts. +final class StubAuthContextFactory: NSObject, FLADAuthContextFactory { + var contexts: [FLADAuthContext] + init(contexts: [FLADAuthContext]) { + self.contexts = contexts + } + + func createAuthContext() -> FLADAuthContext { + XCTAssert(self.contexts.count > 0, "Insufficient test contexts provided") + return self.contexts.removeFirst() + } +} + +final class StubAuthContext: NSObject, FLADAuthContext { + /// Whether calls to this stub are expected to be for biometric authentication. + /// + /// While this object could be set up to return different values for different policies, in + /// practice only one policy is needed by any given test, so this just allows asserting that the + /// code is calling with the intended policy. + var expectBiometrics = false + /// The error to return from canEvaluatePolicy. + var canEvaluateError: NSError? + /// The value to return from evaluatePolicy:error:. + var evaluateResponse = false + /// The error to return from evaluatePolicy:error:. + var evaluateError: NSError? + + // Overridden as read-write to allow stubbing. + var biometryType: LABiometryType = .none + var localizedFallbackTitle: String? + + func canEvaluatePolicy(_ policy: LAPolicy) throws { + XCTAssertEqual( + policy, + expectBiometrics + ? LAPolicy.deviceOwnerAuthenticationWithBiometrics + : LAPolicy.deviceOwnerAuthentication) + if let canEvaluateError = canEvaluateError { + throw canEvaluateError + } + } + + func evaluatePolicy( + _ policy: LAPolicy, localizedReason: String, reply: @escaping (Bool, Error?) -> Void + ) { + XCTAssertEqual( + policy, + expectBiometrics + ? LAPolicy.deviceOwnerAuthenticationWithBiometrics + : LAPolicy.deviceOwnerAuthentication) + // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not + // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on + // a background thread. + DispatchQueue.global(qos: .background).async { + reply(self.evaluateResponse, self.evaluateError) + } + } +} + +// MARK: - + +class FLALocalAuthPluginTests: XCTestCase { + + func testSuccessfullAuthWithBiometrics() throws { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + stubAuthContext.expectBiometrics = true + stubAuthContext.evaluateResponse = true + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: true, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertTrue(Thread.isMainThread) + XCTAssertEqual(resultDetails?.result, FLADAuthResult.success) + XCTAssertNil(error) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testSuccessfullAuthWithoutBiometrics() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + stubAuthContext.evaluateResponse = true + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: false, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertTrue(Thread.isMainThread) + XCTAssertEqual(resultDetails?.result, FLADAuthResult.success) + XCTAssertNil(error) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testFailedAuthWithBiometrics() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + stubAuthContext.expectBiometrics = true + stubAuthContext.evaluateError = NSError( + domain: "error", code: LAError.authenticationFailed.rawValue) + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: true, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertTrue(Thread.isMainThread) + // TODO(stuartmorgan): Fix this; this was the pre-Pigeon-migration + // behavior, so is preserved as part of the migration, but a failed + // authentication should return failure, not an error that results in a + // PlatformException. + XCTAssertEqual(resultDetails?.result, FLADAuthResult.errorNotAvailable) + XCTAssertNil(error) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testFailedWithUnknownErrorCode() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + stubAuthContext.evaluateError = NSError(domain: "error", code: 99) + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: false, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertTrue(Thread.isMainThread) + XCTAssertEqual(resultDetails?.result, FLADAuthResult.errorNotAvailable) + XCTAssertNil(error) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testSystemCancelledWithoutStickyAuth() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + stubAuthContext.evaluateError = NSError(domain: "error", code: LAError.systemCancel.rawValue) + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: false, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertTrue(Thread.isMainThread) + XCTAssertEqual(resultDetails?.result, FLADAuthResult.failure) + XCTAssertNil(error) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testFailedAuthWithoutBiometrics() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + stubAuthContext.evaluateError = NSError( + domain: "error", code: LAError.authenticationFailed.rawValue) + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: false, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertTrue(Thread.isMainThread) + // TODO(stuartmorgan): Fix this; this was the pre-Pigeon-migration + // behavior, so is preserved as part of the migration, but a failed + // authentication should return failure, not an error that results in a + // PlatformException. + XCTAssertEqual(resultDetails?.result, FLADAuthResult.errorNotAvailable) + XCTAssertNil(error) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testLocalizedFallbackTitle() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + strings.localizedFallbackTitle = "a title" + stubAuthContext.evaluateResponse = true + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: false, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertEqual( + stubAuthContext.localizedFallbackTitle, + strings.localizedFallbackTitle) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testSkippedLocalizedFallbackTitle() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + let strings = createAuthStrings() + strings.localizedFallbackTitle = nil + stubAuthContext.evaluateResponse = true + + let expectation = expectation(description: "Result is called") + plugin.authenticate( + with: FLADAuthOptions.make( + withBiometricOnly: false, + sticky: false, + useErrorDialogs: false), + strings: strings + ) { resultDetails, error in + XCTAssertNil(stubAuthContext.localizedFallbackTitle) + expectation.fulfill() + } + self.waitForExpectations(timeout: timeout) + } + + func testDeviceSupportsBiometrics_withEnrolledHardware() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + stubAuthContext.expectBiometrics = true + + var error: FlutterError? + let result = plugin.deviceCanSupportBiometricsWithError(&error) + XCTAssertTrue(result!.boolValue) + XCTAssertNil(error) + } + + func testDeviceSupportsBiometrics_withNonEnrolledHardware() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + stubAuthContext.expectBiometrics = true + stubAuthContext.canEvaluateError = NSError( + domain: "error", code: LAError.biometryNotEnrolled.rawValue) + + var error: FlutterError? + let result = plugin.deviceCanSupportBiometricsWithError(&error) + XCTAssertTrue(result!.boolValue) + XCTAssertNil(error) + } + + func testDeviceSupportsBiometrics_withNoBiometricHardware() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + stubAuthContext.expectBiometrics = true + stubAuthContext.canEvaluateError = NSError(domain: "error", code: 0) + + var error: FlutterError? + let result = plugin.deviceCanSupportBiometricsWithError(&error) + XCTAssertFalse(result!.boolValue) + XCTAssertNil(error) + } + + func testGetEnrolledBiometricsWithFaceID() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + stubAuthContext.expectBiometrics = true + stubAuthContext.biometryType = .faceID + + var error: FlutterError? + let result = plugin.getEnrolledBiometricsWithError(&error) + XCTAssertEqual(result!.count, 1) + XCTAssertEqual(result![0].value, FLADAuthBiometric.face) + XCTAssertNil(error) + } + + func testGetEnrolledBiometricsWithTouchID() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + stubAuthContext.expectBiometrics = true + stubAuthContext.biometryType = .touchID + + var error: FlutterError? + let result = plugin.getEnrolledBiometricsWithError(&error) + XCTAssertEqual(result!.count, 1) + XCTAssertEqual(result![0].value, FLADAuthBiometric.fingerprint) + XCTAssertNil(error) + } + + func testGetEnrolledBiometricsWithoutEnrolledHardware() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + stubAuthContext.expectBiometrics = true + stubAuthContext.canEvaluateError = NSError( + domain: "error", code: LAError.biometryNotEnrolled.rawValue) + + var error: FlutterError? + let result = plugin.getEnrolledBiometricsWithError(&error) + XCTAssertTrue(result!.isEmpty) + XCTAssertNil(error) + } + + func testIsDeviceSupportedHandlesSupported() { + let stubAuthContext = StubAuthContext() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + var error: FlutterError? + let result = plugin.isDeviceSupportedWithError(&error) + XCTAssertTrue(result!.boolValue) + XCTAssertNil(error) + } + + func testIsDeviceSupportedHandlesUnsupported() { + let stubAuthContext = StubAuthContext() + // An arbitrary error to cause canEvaluatePolicy to return false. + stubAuthContext.canEvaluateError = NSError(domain: "error", code: 1) + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext])) + + var error: FlutterError? + let result = plugin.isDeviceSupportedWithError(&error) + XCTAssertFalse(result!.boolValue) + XCTAssertNil(error) + } + + // Creates an FLADAuthStrings with placeholder values. + func createAuthStrings() -> FLADAuthStrings { + return FLADAuthStrings.make( + withReason: "a reason", lockOut: "locked out", goToSettingsButton: "Go To Settings", + goToSettingsDescription: "Settings", cancelButton: "Cancel", localizedFallbackTitle: nil) + } + +} diff --git a/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin.podspec b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin.podspec index 38dd90cf105..e3947dd2817 100644 --- a/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin.podspec +++ b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin.podspec @@ -14,11 +14,10 @@ Downloaded by pub (not CocoaPods). s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/local_auth' } s.documentation_url = 'https://pub.dev/packages/local_auth_darwin' - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' + s.source_files = 'local_auth_darwin/Sources/local_auth_darwin/**/*.{h,m}' + s.public_header_files = 'local_auth_darwin/Sources/local_auth_darwin/include/**/*.h' s.dependency 'Flutter' s.platform = :ios, '12.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.resource_bundles = {'local_auth_darwin_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'local_auth_darwin_privacy' => ['local_auth_darwin/Sources/local_auth_darwin/Resources/PrivacyInfo.xcprivacy']} end - diff --git a/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Package.swift b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Package.swift new file mode 100644 index 00000000000..952abfeac80 --- /dev/null +++ b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "local_auth_darwin", + platforms: [ + .iOS("12.0"), + .macOS("10.14"), + ], + products: [ + .library(name: "local-auth-darwin", targets: ["local_auth_darwin"]) + ], + dependencies: [], + targets: [ + .target( + name: "local_auth_darwin", + dependencies: [], + resources: [ + .process("Resources") + ], + cSettings: [ + .headerSearchPath("include/local_auth_darwin") + ] + ) + ] +) diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m similarity index 83% rename from packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m rename to packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m index e57adcdff30..c8bc3598085 100644 --- a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.m +++ b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m @@ -1,20 +1,68 @@ // 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 "FLALocalAuthPlugin.h" -#import "FLALocalAuthPlugin_Test.h" +#import "./include/local_auth_darwin/FLALocalAuthPlugin.h" +#import "./include/local_auth_darwin/FLALocalAuthPlugin_Test.h" #import typedef void (^FLADAuthCompletion)(FLADAuthResultDetails *_Nullable, FlutterError *_Nullable); +/// A default auth context that wraps LAContext. +// TODO(stuartmorgan): When converting to Swift, eliminate this class and use an extension to make +// LAContext declare conformance to FLADAuthContext. +@interface FLADefaultAuthContext : NSObject +/// Returns a wrapper for the given LAContext. +- (instancetype)initWithContext:(LAContext *)context NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +/// The wrapped auth context. +@property(nonatomic) LAContext *context; +@end + +@implementation FLADefaultAuthContext +- (instancetype)initWithContext:(LAContext *)context { + self = [super init]; + if (self) { + _context = context; + } + return self; +} + +#pragma mark FLADAuthContext implementation + +- (NSString *)localizedFallbackTitle { + return self.context.localizedFallbackTitle; +} + +- (void)setLocalizedFallbackTitle:(NSString *)localizedFallbackTitle { + self.context.localizedFallbackTitle = localizedFallbackTitle; +} + +- (LABiometryType)biometryType { + return self.context.biometryType; +} + +- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError *__autoreleasing *)error { + return [self.context canEvaluatePolicy:policy error:error]; +} + +- (void)evaluatePolicy:(LAPolicy)policy + localizedReason:(NSString *)localizedReason + reply:(void (^)(BOOL success, NSError *__nullable error))reply { + [self.context evaluatePolicy:policy localizedReason:localizedReason reply:reply]; +} + +@end + /// A default context factory that wraps standard LAContext allocation. @interface FLADefaultAuthContextFactory : NSObject @end @implementation FLADefaultAuthContextFactory -- (LAContext *)createAuthContext { - return [[LAContext alloc] init]; +- (id)createAuthContext { + // TODO(stuartmorgan): When converting to Swift, just return LAContext here. + return [[FLADefaultAuthContext alloc] initWithContext:[[LAContext alloc] init]]; } @end @@ -77,7 +125,7 @@ - (void)authenticateWithOptions:(nonnull FLADAuthOptions *)options strings:(nonnull FLADAuthStrings *)strings completion:(nonnull void (^)(FLADAuthResultDetails *_Nullable, FlutterError *_Nullable))completion { - LAContext *context = [self.authContextFactory createAuthContext]; + id context = [self.authContextFactory createAuthContext]; NSError *authError = nil; self.lastCallState = nil; context.localizedFallbackTitle = strings.localizedFallbackTitle; @@ -103,7 +151,7 @@ - (void)authenticateWithOptions:(nonnull FLADAuthOptions *)options - (nullable NSNumber *)deviceCanSupportBiometricsWithError: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - LAContext *context = [self.authContextFactory createAuthContext]; + id context = [self.authContextFactory createAuthContext]; NSError *authError = nil; // Check if authentication with biometrics is possible. if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics @@ -124,7 +172,7 @@ - (nullable NSNumber *)deviceCanSupportBiometricsWithError: - (nullable NSArray *)getEnrolledBiometricsWithError: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - LAContext *context = [self.authContextFactory createAuthContext]; + id context = [self.authContextFactory createAuthContext]; NSError *authError = nil; NSMutableArray *biometrics = [[NSMutableArray alloc] init]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics @@ -143,7 +191,7 @@ - (nullable NSNumber *)deviceCanSupportBiometricsWithError: - (nullable NSNumber *)isDeviceSupportedWithError: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - LAContext *context = [self.authContextFactory createAuthContext]; + id context = [self.authContextFactory createAuthContext]; return @([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:NULL]); } diff --git a/packages/local_auth/local_auth_darwin/darwin/Resources/PrivacyInfo.xcprivacy b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/local_auth/local_auth_darwin/darwin/Resources/PrivacyInfo.xcprivacy rename to packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/Resources/PrivacyInfo.xcprivacy diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.h b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/FLALocalAuthPlugin.h similarity index 100% rename from packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin.h rename to packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/FLALocalAuthPlugin.h diff --git a/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/FLALocalAuthPlugin_Test.h b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/FLALocalAuthPlugin_Test.h new file mode 100644 index 00000000000..cfba07b8fb4 --- /dev/null +++ b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/FLALocalAuthPlugin_Test.h @@ -0,0 +1,35 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/// Protocol for interacting with LAContext instances, abstracted to allow using mock/fake instances +/// in unit tests. +@protocol FLADAuthContext +@required +@property(nonatomic, nullable, copy) NSString *localizedFallbackTitle; +@property(nonatomic, readonly) LABiometryType biometryType; +- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError *__autoreleasing *)error; +- (void)evaluatePolicy:(LAPolicy)policy + localizedReason:(NSString *)localizedReason + reply:(void (^)(BOOL success, NSError *__nullable error))reply; +@end + +/// Protocol for a source of FLADAuthContext instances. Used to allow context injection in unit +/// tests. +@protocol FLADAuthContextFactory +@required +- (id)createAuthContext; +@end + +@interface FLALocalAuthPlugin () +/// Returns an instance that uses the given factory to create LAContexts. +- (instancetype)initWithContextFactory:(NSObject *)factory + NS_DESIGNATED_INITIALIZER; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/messages.g.h b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/messages.g.h similarity index 100% rename from packages/local_auth/local_auth_darwin/darwin/Classes/messages.g.h rename to packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/messages.g.h diff --git a/packages/local_auth/local_auth_darwin/darwin/Classes/messages.g.m b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/messages.g.m similarity index 98% rename from packages/local_auth/local_auth_darwin/darwin/Classes/messages.g.m rename to packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/messages.g.m index 421b31e9424..6ab8ee5edf9 100644 --- a/packages/local_auth/local_auth_darwin/darwin/Classes/messages.g.m +++ b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/messages.g.m @@ -4,7 +4,9 @@ // Autogenerated from Pigeon (v13.1.2), do not edit directly. // See also: https://pub.dev/packages/pigeon -#import "messages.g.h" +// The line below is manually edited. See: +// https://github.com/flutter/flutter/issues/147587 +#import "./include/local_auth_darwin/messages.g.h" #if TARGET_OS_OSX #import diff --git a/packages/local_auth/local_auth_darwin/example/ios/Podfile b/packages/local_auth/local_auth_darwin/example/ios/Podfile index 196252cf8af..c66ac99f75a 100644 --- a/packages/local_auth/local_auth_darwin/example/ios/Podfile +++ b/packages/local_auth/local_auth_darwin/example/ios/Podfile @@ -31,8 +31,6 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths - - pod 'OCMock','3.5' end end diff --git a/packages/local_auth/local_auth_darwin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/local_auth/local_auth_darwin/example/ios/Runner.xcodeproj/project.pbxproj index 8ef2595364c..8645071ff16 100644 --- a/packages/local_auth/local_auth_darwin/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/local_auth/local_auth_darwin/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,13 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ 0CCCD07A2CE24E13C9C1EEA4 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3398D2E426164AD8005A052F /* FLALocalAuthPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3398D2E326164AD8005A052F /* FLALocalAuthPluginTests.m */; }; + 338A5F9D2BFBA45B00DF0C4E /* FLALocalAuthPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 338A5F9C2BFBA45B00DF0C4E /* FLALocalAuthPluginTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 691CB38B382734AF80FBCA4C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBFA21B380E07A3A585383D /* libPods-RunnerTests.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; @@ -45,11 +45,11 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 338A5F9C2BFBA45B00DF0C4E /* FLALocalAuthPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FLALocalAuthPluginTests.swift; path = ../../../darwin/Tests/FLALocalAuthPluginTests.swift; sourceTree = ""; }; 3398D2CD26163948005A052F /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3398D2D126163948005A052F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3398D2DC261649CD005A052F /* liblocal_auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = liblocal_auth.a; sourceTree = BUILT_PRODUCTS_DIR; }; 3398D2DF26164A03005A052F /* liblocal_auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = liblocal_auth.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 3398D2E326164AD8005A052F /* FLALocalAuthPluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FLALocalAuthPluginTests.m; path = ../../darwin/Tests/FLALocalAuthPluginTests.m; sourceTree = SOURCE_ROOT; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 658CDD04B21E4EA92F8EF229 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -93,7 +93,7 @@ 33BF11D226680B2E002967F3 /* RunnerTests */ = { isa = PBXGroup; children = ( - 3398D2E326164AD8005A052F /* FLALocalAuthPluginTests.m */, + 338A5F9C2BFBA45B00DF0C4E /* FLALocalAuthPluginTests.swift */, 3398D2D126163948005A052F /* Info.plist */, ); path = RunnerTests; @@ -232,6 +232,7 @@ TargetAttributes = { 3398D2CC26163948005A052F = { CreatedOnToolsVersion = 12.4; + LastSwiftMigration = 1510; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -377,7 +378,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3398D2E426164AD8005A052F /* FLALocalAuthPluginTests.m in Sources */, + 338A5F9D2BFBA45B00DF0C4E /* FLALocalAuthPluginTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -428,6 +429,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -444,6 +446,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; @@ -456,6 +460,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -471,6 +476,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; diff --git a/packages/local_auth/local_auth_darwin/lib/local_auth_darwin.dart b/packages/local_auth/local_auth_darwin/lib/local_auth_darwin.dart index 4d0beabdefe..240aa6e6018 100644 --- a/packages/local_auth/local_auth_darwin/lib/local_auth_darwin.dart +++ b/packages/local_auth/local_auth_darwin/lib/local_auth_darwin.dart @@ -43,7 +43,7 @@ class LocalAuthDarwin extends LocalAuthPlatform { _pigeonStringsFromAuthMessages(localizedReason, authMessages)); // TODO(stuartmorgan): Replace this with structured errors, coordinated // across all platform implementations, per - // https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#platform-exception-handling + // https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling // The PlatformExceptions thrown here are for compatibiilty with the // previous Objective-C implementation. switch (resultDetails.result) { diff --git a/packages/local_auth/local_auth_darwin/pigeons/messages.dart b/packages/local_auth/local_auth_darwin/pigeons/messages.dart index 15be1f043f3..121e8988651 100644 --- a/packages/local_auth/local_auth_darwin/pigeons/messages.dart +++ b/packages/local_auth/local_auth_darwin/pigeons/messages.dart @@ -6,9 +6,12 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - objcHeaderOut: 'darwin/Classes/messages.g.h', - objcSourceOut: 'darwin/Classes/messages.g.m', + objcHeaderOut: + 'darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/messages.g.h', + objcSourceOut: + 'darwin/local_auth_darwin/Sources/local_auth_darwin/messages.g.m', objcOptions: ObjcOptions( + headerIncludePath: './include/local_auth_darwin/messages.g.h', prefix: 'FLAD', // Avoid runtime collisions with old local_auth_ios classes. ), copyrightHeader: 'pigeons/copyright.txt', diff --git a/packages/local_auth/local_auth_darwin/pubspec.yaml b/packages/local_auth/local_auth_darwin/pubspec.yaml index e4482d2eb9b..05a6be6cd59 100644 --- a/packages/local_auth/local_auth_darwin/pubspec.yaml +++ b/packages/local_auth/local_auth_darwin/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_darwin description: iOS implementation of the local_auth plugin. repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth_darwin issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.2.2 +version: 1.3.1 environment: sdk: ^3.2.3 diff --git a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md index b4f21982f61..7317947e8d8 100644 --- a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md +++ b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 1.0.10 diff --git a/packages/local_auth/local_auth_platform_interface/pubspec.yaml b/packages/local_auth/local_auth_platform_interface/pubspec.yaml index 02891020085..5e34a426352 100644 --- a/packages/local_auth/local_auth_platform_interface/pubspec.yaml +++ b/packages/local_auth/local_auth_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.10 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_windows/CHANGELOG.md b/packages/local_auth/local_auth_windows/CHANGELOG.md index 9da32d2c0f3..46d66bac361 100644 --- a/packages/local_auth/local_auth_windows/CHANGELOG.md +++ b/packages/local_auth/local_auth_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 1.0.10 diff --git a/packages/local_auth/local_auth_windows/README.md b/packages/local_auth/local_auth_windows/README.md index 64c5446f77c..dd6c4aca603 100644 --- a/packages/local_auth/local_auth_windows/README.md +++ b/packages/local_auth/local_auth_windows/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/local_auth -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/local_auth/local_auth_windows/example/pubspec.yaml b/packages/local_auth/local_auth_windows/example/pubspec.yaml index a1b504427ed..4bdb1ebcaa2 100644 --- a/packages/local_auth/local_auth_windows/example/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the local_auth_windows plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_windows/pubspec.yaml b/packages/local_auth/local_auth_windows/pubspec.yaml index ac9fee60438..52a4240fa8a 100644 --- a/packages/local_auth/local_auth_windows/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.10 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/metrics_center/CHANGELOG.md b/packages/metrics_center/CHANGELOG.md index a661251dc1d..fd91d632989 100644 --- a/packages/metrics_center/CHANGELOG.md +++ b/packages/metrics_center/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 1.0.13 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/metrics_center/pubspec.yaml b/packages/metrics_center/pubspec.yaml index 581be39e283..21f60340f44 100644 --- a/packages/metrics_center/pubspec.yaml +++ b/packages/metrics_center/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/metrics_cente issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+metrics_center%22 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: _discoveryapis_commons: ^1.0.0 diff --git a/packages/multicast_dns/CHANGELOG.md b/packages/multicast_dns/CHANGELOG.md index 1ea1abcd63e..6db175a0db9 100644 --- a/packages/multicast_dns/CHANGELOG.md +++ b/packages/multicast_dns/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.3.2+7 -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Optimized Socket Binding: Always bind to 0.0.0.0 for simplicity and efficiency. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.3.2+6 diff --git a/packages/multicast_dns/README.md b/packages/multicast_dns/README.md index 10cd33e4bb6..f30ce16936e 100644 --- a/packages/multicast_dns/README.md +++ b/packages/multicast_dns/README.md @@ -1,5 +1,7 @@ # Multicast DNS package +Based on [RFC 6762 Multicast DNS](https://datatracker.ietf.org/doc/html/rfc6762). + [![pub package](https://img.shields.io/pub/v/multicast_dns.svg)]( https://pub.dartlang.org/packages/multicast_dns) @@ -7,8 +9,5 @@ A Dart package to do service discovery over multicast DNS (mDNS), Bonjour, and A ## Usage -To use this package, add `multicast_dns` as a -[dependency in your pubspec.yaml file](https://pub.dev/packages/multicast_dns/install). - [The example](https://pub.dev/packages/multicast_dns/example) demonstrates how to use the `MDnsClient` Dart class in your code. diff --git a/packages/multicast_dns/lib/multicast_dns.dart b/packages/multicast_dns/lib/multicast_dns.dart index 336ac427930..d6e2e6b06c0 100644 --- a/packages/multicast_dns/lib/multicast_dns.dart +++ b/packages/multicast_dns/lib/multicast_dns.dart @@ -48,8 +48,8 @@ class MDnsClient { bool _starting = false; bool _started = false; - final List _sockets = []; - final List _toBeClosed = []; + RawDatagramSocket? _incomingIPv4; + final List _ipv6InterfaceSockets = []; final LookupResolver _resolver = LookupResolver(); final ResourceRecordCache _cache = ResourceRecordCache(); final RawDatagramSocketFactory _rawDatagramSocketFactory; @@ -117,9 +117,9 @@ class MDnsClient { // Can't send to IPv6 any address. if (incoming.address != InternetAddress.anyIPv6) { - _sockets.add(incoming); + _incomingIPv4 = incoming; } else { - _toBeClosed.add(incoming); + _ipv6InterfaceSockets.add(incoming); } _mDnsAddress ??= incoming.address.type == InternetAddressType.IPv4 @@ -130,30 +130,25 @@ class MDnsClient { (await interfacesFactory(listenAddress.type)).toList(); for (final NetworkInterface interface in interfaces) { - // Create a socket for sending on each adapter. final InternetAddress targetAddress = interface.addresses[0]; - final RawDatagramSocket socket = await _rawDatagramSocketFactory( - targetAddress, - selectedMDnsPort, - reuseAddress: true, - reusePort: true, - ttl: 255, - ); - _sockets.add(socket); + // Ensure that we're using this address/interface for multicast. - if (targetAddress.type == InternetAddressType.IPv4) { - socket.setRawOption(RawSocketOption( - RawSocketOption.levelIPv4, - RawSocketOption.IPv4MulticastInterface, - targetAddress.rawAddress, - )); - } else { + if (targetAddress.type == InternetAddressType.IPv6) { + final RawDatagramSocket socket = await _rawDatagramSocketFactory( + targetAddress, + selectedMDnsPort, + reuseAddress: true, + reusePort: true, + ttl: 255, + ); + _ipv6InterfaceSockets.add(socket); socket.setRawOption(RawSocketOption.fromInt( RawSocketOption.levelIPv6, RawSocketOption.IPv6MulticastInterface, interface.index, )); } + // Join multicast on this interface. incoming.joinMulticast(_mDnsAddress!, interface); } @@ -171,15 +166,13 @@ class MDnsClient { throw StateError('Cannot stop mDNS client while it is starting.'); } - for (final RawDatagramSocket socket in _sockets) { - socket.close(); - } - _sockets.clear(); + _incomingIPv4?.close(); + _incomingIPv4 = null; - for (final RawDatagramSocket socket in _toBeClosed) { + for (final RawDatagramSocket socket in _ipv6InterfaceSockets) { socket.close(); } - _toBeClosed.clear(); + _ipv6InterfaceSockets.clear(); _resolver.clearPendingRequests(); @@ -219,11 +212,17 @@ class MDnsClient { final Stream results = _resolver.addPendingRequest( query.resourceRecordType, query.fullyQualifiedName, timeout); - // Send the request on all interfaces. final List packet = query.encode(); - for (final RawDatagramSocket socket in _sockets) { - socket.send(packet, _mDnsAddress!, selectedMDnsPort); + + if (_mDnsAddress?.type == InternetAddressType.IPv4) { + // Send and listen on same "ANY" interface + _incomingIPv4?.send(packet, _mDnsAddress!, selectedMDnsPort); + } else { + for (final RawDatagramSocket socket in _ipv6InterfaceSockets) { + socket.send(packet, _mDnsAddress!, selectedMDnsPort); + } } + return results; } diff --git a/packages/multicast_dns/pubspec.yaml b/packages/multicast_dns/pubspec.yaml index 301de701e49..6621352cc92 100644 --- a/packages/multicast_dns/pubspec.yaml +++ b/packages/multicast_dns/pubspec.yaml @@ -2,10 +2,10 @@ name: multicast_dns description: Dart package for performing mDNS queries (e.g. Bonjour, Avahi). repository: https://github.com/flutter/packages/tree/main/packages/multicast_dns issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+multicast_dns%22 -version: 0.3.2+6 +version: 0.3.2+7 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: meta: ^1.3.0 diff --git a/packages/multicast_dns/test/client_test.dart b/packages/multicast_dns/test/client_test.dart index 134d7518d86..f3047e5deee 100644 --- a/packages/multicast_dns/test/client_test.dart +++ b/packages/multicast_dns/test/client_test.dart @@ -86,6 +86,78 @@ void main() { await client.start(); await client.lookup(ResourceRecordQuery.serverPointer('_')).toList(); }); + + group('Bind a single socket to ANY IPv4 and more than one when IPv6', () { + final List> testCases = >[ + { + 'name': 'IPv4', + 'datagramSocketType': InternetAddress.anyIPv4, + 'interfacePrefix': '192.168.2.' + }, + { + 'name': 'IPv6', + 'datagramSocketType': InternetAddress.anyIPv6, + 'interfacePrefix': '2001:0db8:85a3:0000:0000:8a2e:7335:030' + } + ]; + + for (final Map testCase in testCases) { + test('Bind a single socket to ANY ${testCase["name"]}', () async { + final FakeRawDatagramSocket datagramSocket = FakeRawDatagramSocket(); + + datagramSocket.address = + testCase['datagramSocketType']! as InternetAddress; + + final List selectedInterfacesForSendingPackets = []; + final MDnsClient client = MDnsClient(rawDatagramSocketFactory: + (dynamic host, int port, + {bool reuseAddress = true, + bool reusePort = true, + int ttl = 1}) async { + selectedInterfacesForSendingPackets.add(host); + return datagramSocket; + }); + + const int numberOfFakeInterfaces = 10; + Future> fakeNetworkInterfacesFactory( + InternetAddressType type) async { + final List fakeInterfaces = []; + + // Generate "fake" interfaces + for (int i = 0; i < numberOfFakeInterfaces; i++) { + fakeInterfaces.add(FakeNetworkInterface( + 'inetfake$i', + [ + InternetAddress("${testCase['interfacePrefix']! as String}$i") + ], + 0, + )); + } + + // ignore: always_specify_types + return Future.value(fakeInterfaces); + } + + final InternetAddress listenAddress = + testCase['datagramSocketType']! as InternetAddress; + + await client.start( + listenAddress: listenAddress, + mDnsPort: 1234, + interfacesFactory: fakeNetworkInterfacesFactory); + client.stop(); + + if (testCase['datagramSocketType'] == InternetAddress.anyIPv4) { + expect(selectedInterfacesForSendingPackets.length, 1); + } else { + // + 1 because of unspecified address (::) + expect(selectedInterfacesForSendingPackets.length, + numberOfFakeInterfaces + 1); + } + expect(selectedInterfacesForSendingPackets[0], listenAddress.address); + }); + } + }); } class FakeRawDatagramSocket extends Fake implements RawDatagramSocket { @@ -113,4 +185,30 @@ class FakeRawDatagramSocket extends Fake implements RawDatagramSocket { int send(List buffer, InternetAddress address, int port) { return buffer.length; } + + @override + void joinMulticast(InternetAddress group, [NetworkInterface? interface]) { + // nothing to do here + } + @override + void setRawOption(RawSocketOption option) { + // nothing to do here + } +} + +class FakeNetworkInterface implements NetworkInterface { + FakeNetworkInterface(this._name, this._addresses, this._index); + + final String _name; + final List _addresses; + final int _index; + + @override + List get addresses => _addresses; + + @override + String get name => _name; + + @override + int get index => _index; } diff --git a/packages/palette_generator/CHANGELOG.md b/packages/palette_generator/CHANGELOG.md index ffa196e4f51..88f3a48c306 100644 --- a/packages/palette_generator/CHANGELOG.md +++ b/packages/palette_generator/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.3.3+3 diff --git a/packages/palette_generator/README.md b/packages/palette_generator/README.md index 3d378811745..9dbfd45e9b5 100644 --- a/packages/palette_generator/README.md +++ b/packages/palette_generator/README.md @@ -5,11 +5,6 @@ A Flutter package to extract prominent colors from an Image, typically used to find colors for a user interface. -## Usage - -To use this package, add `palette_generator` as a -[dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). - ## Example Import the library via diff --git a/packages/palette_generator/example/android/app/src/main/AndroidManifest.xml b/packages/palette_generator/example/android/app/src/main/AndroidManifest.xml index cce53eff225..7399bc62dd5 100644 --- a/packages/palette_generator/example/android/app/src/main/AndroidManifest.xml +++ b/packages/palette_generator/example/android/app/src/main/AndroidManifest.xml @@ -17,13 +17,6 @@ android:hardwareAccelerated="true" android:exported="true" android:windowSoftInputMode="adjustResize"> - - diff --git a/packages/palette_generator/example/android/build.gradle b/packages/palette_generator/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/palette_generator/example/android/build.gradle +++ b/packages/palette_generator/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/palette_generator/example/android/settings.gradle b/packages/palette_generator/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/palette_generator/example/android/settings.gradle +++ b/packages/palette_generator/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/palette_generator/example/pubspec.yaml b/packages/palette_generator/example/pubspec.yaml index 9c91033e284..c9963cbbcbd 100644 --- a/packages/palette_generator/example/pubspec.yaml +++ b/packages/palette_generator/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: none version: 0.1.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/palette_generator/pubspec.yaml b/packages/palette_generator/pubspec.yaml index 7c7dabcc6cc..07e682c4159 100644 --- a/packages/palette_generator/pubspec.yaml +++ b/packages/palette_generator/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.3.3+3 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: collection: ^1.15.0 diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md index 23ad8544bce..63d58809c21 100644 --- a/packages/path_provider/path_provider/CHANGELOG.md +++ b/packages/path_provider/path_provider/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.1.3 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md index 3c0e5b2fa86..5a4bfe4a6ce 100644 --- a/packages/path_provider/path_provider/README.md +++ b/packages/path_provider/path_provider/README.md @@ -11,10 +11,6 @@ Not all methods are supported on all platforms. |-------------|---------|-------|-------|--------|-------------| | **Support** | SDK 16+ | 12.0+ | Any | 10.14+ | Windows 10+ | -## Usage - -To use this plugin, add `path_provider` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). - ## Example ```dart diff --git a/packages/path_provider/path_provider/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/path_provider/path_provider/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/path_provider/path_provider/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/path_provider/path_provider/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/path_provider/path_provider/example/android/build.gradle b/packages/path_provider/path_provider/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/path_provider/path_provider/example/android/build.gradle +++ b/packages/path_provider/path_provider/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/path_provider/path_provider/example/android/settings.gradle b/packages/path_provider/path_provider/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/path_provider/path_provider/example/android/settings.gradle +++ b/packages/path_provider/path_provider/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart index 9a4a5d6b21f..846d87df399 100644 --- a/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart +++ b/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart @@ -17,7 +17,15 @@ void main() { testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { final Directory result = await getApplicationDocumentsDirectory(); - _verifySampleFile(result, 'applicationDocuments'); + if (Platform.isMacOS) { + // _verifySampleFile causes hangs in driver when sandboxing is disabled + // because the path changes from an app specific directory to + // ~/Documents, which requires additional permissions to access on macOS. + // Instead, validate that a non-empty path was returned. + expect(result.path, isNotEmpty); + } else { + _verifySampleFile(result, 'applicationDocuments'); + } }); testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { diff --git a/packages/path_provider/path_provider/example/macos/Runner/DebugProfile.entitlements b/packages/path_provider/path_provider/example/macos/Runner/DebugProfile.entitlements index f83e1f42d12..727d49f3df8 100644 --- a/packages/path_provider/path_provider/example/macos/Runner/DebugProfile.entitlements +++ b/packages/path_provider/path_provider/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/path_provider/path_provider/example/macos/Runner/Release.entitlements b/packages/path_provider/path_provider/example/macos/Runner/Release.entitlements index 9d379927fbc..8528f1145fe 100644 --- a/packages/path_provider/path_provider/example/macos/Runner/Release.entitlements +++ b/packages/path_provider/path_provider/example/macos/Runner/Release.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.files.downloads.read-write diff --git a/packages/path_provider/path_provider/example/pubspec.yaml b/packages/path_provider/path_provider/example/pubspec.yaml index d05b5236fac..60ba6783f7f 100644 --- a/packages/path_provider/path_provider/example/pubspec.yaml +++ b/packages/path_provider/path_provider/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the path_provider plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index 921de17abce..c6a07a768ed 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.1.3 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_android/CHANGELOG.md b/packages/path_provider/path_provider_android/CHANGELOG.md index 5ca920b92f2..30d7c22190a 100644 --- a/packages/path_provider/path_provider_android/CHANGELOG.md +++ b/packages/path_provider/path_provider_android/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.2.6 + +* Updates annotations lib to 1.8.0. + +## 2.2.5 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 2.2.4 * Updates minSdkVersion version to 19. diff --git a/packages/path_provider/path_provider_android/README.md b/packages/path_provider/path_provider_android/README.md index 3bfc4255f08..a92c41e2b93 100644 --- a/packages/path_provider/path_provider_android/README.md +++ b/packages/path_provider/path_provider_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/path_provider -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/path_provider/path_provider_android/android/build.gradle b/packages/path_provider/path_provider_android/android/build.gradle index 566b90ef760..84d85748e23 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.7.1' + implementation 'androidx.annotation:annotation:1.8.0' testImplementation 'junit:junit:4.13.2' } diff --git a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java index a29fe9b9044..ce23e545d61 100644 --- a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java +++ b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java @@ -32,13 +32,6 @@ private void setup(BinaryMessenger messenger, Context context) { this.context = context; } - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - PathProviderPlugin instance = new PathProviderPlugin(); - instance.setup(registrar.messenger(), registrar.context()); - } - @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { setup(binding.getBinaryMessenger(), binding.getApplicationContext()); diff --git a/packages/path_provider/path_provider_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/path_provider/path_provider_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/path_provider/path_provider_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/path_provider/path_provider_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/path_provider/path_provider_android/example/android/build.gradle b/packages/path_provider/path_provider_android/example/android/build.gradle index d3f76d61183..fe7f3663afc 100644 --- a/packages/path_provider/path_provider_android/example/android/build.gradle +++ b/packages/path_provider/path_provider_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/path_provider/path_provider_android/example/android/settings.gradle b/packages/path_provider/path_provider_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/path_provider/path_provider_android/example/android/settings.gradle +++ b/packages/path_provider/path_provider_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/path_provider/path_provider_android/example/pubspec.yaml b/packages/path_provider/path_provider_android/example/pubspec.yaml index 3512a22302b..54601fb67d7 100644 --- a/packages/path_provider/path_provider_android/example/pubspec.yaml +++ b/packages/path_provider/path_provider_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the path_provider plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml index eed16592e60..a0e2bd05f05 100644 --- a/packages/path_provider/path_provider_android/pubspec.yaml +++ b/packages/path_provider/path_provider_android/pubspec.yaml @@ -2,11 +2,11 @@ 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.4 +version: 2.2.6 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md index 9d505e54fb9..760b40f7d06 100644 --- a/packages/path_provider/path_provider_foundation/CHANGELOG.md +++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.4.0 +* Adds Swift Package Manager compatibility. * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. ## 2.3.2 diff --git a/packages/path_provider/path_provider_foundation/README.md b/packages/path_provider/path_provider_foundation/README.md index 5a01ace4b1e..4aff670a1e6 100644 --- a/packages/path_provider/path_provider_foundation/README.md +++ b/packages/path_provider/path_provider_foundation/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/path_provider -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec index 30a8f13b8c4..8634ab149dc 100644 --- a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation' } - s.source_files = 'Classes/**/*' + s.source_files = 'path_provider_foundation/Sources/path_provider_foundation/**/*.swift' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment_target = '12.0' @@ -22,5 +22,5 @@ Pod::Spec.new do |s| 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } s.swift_version = '5.0' - s.resource_bundles = {'path_provider_foundation_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'path_provider_foundation_privacy' => ['path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Package.swift b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Package.swift new file mode 100644 index 00000000000..e0e82689ac8 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import PackageDescription + +let package = Package( + name: "path_provider_foundation", + platforms: [ + .iOS("12.0"), + .macOS("10.14"), + ], + products: [ + .library(name: "path-provider-foundation", targets: ["path_provider_foundation"]) + ], + dependencies: [], + targets: [ + .target( + name: "path_provider_foundation", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/PathProviderPlugin.swift similarity index 100% rename from packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift rename to packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/PathProviderPlugin.swift diff --git a/packages/path_provider/path_provider_foundation/darwin/Resources/PrivacyInfo.xcprivacy b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/path_provider/path_provider_foundation/darwin/Resources/PrivacyInfo.xcprivacy rename to packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy diff --git a/packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift similarity index 100% rename from packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift rename to packages/path_provider/path_provider_foundation/darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift diff --git a/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart index 0d05745127a..4436dc55f8f 100644 --- a/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart +++ b/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart @@ -20,7 +20,15 @@ void main() { testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { final PathProviderPlatform provider = PathProviderPlatform.instance; final String? result = await provider.getApplicationDocumentsPath(); - _verifySampleFile(result, 'applicationDocuments'); + if (Platform.isMacOS) { + // _verifySampleFile causes hangs in driver when sandboxing is disabled + // because the path changes from an app specific directory to + // ~/Documents, which requires additional permissions to access on macOS. + // Instead, validate that a non-empty path was returned. + expect(result, isNotEmpty); + } else { + _verifySampleFile(result, 'applicationDocuments'); + } }); testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { diff --git a/packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements b/packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements index 8139952b3e5..f1461605a91 100644 --- a/packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements +++ b/packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements b/packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements index 2f9659c917f..bff50d8839c 100644 --- a/packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements +++ b/packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.files.downloads.read-write diff --git a/packages/path_provider/path_provider_foundation/pigeons/messages.dart b/packages/path_provider/path_provider_foundation/pigeons/messages.dart index ae4e7c144b3..c7d5fdb834f 100644 --- a/packages/path_provider/path_provider_foundation/pigeons/messages.dart +++ b/packages/path_provider/path_provider_foundation/pigeons/messages.dart @@ -5,7 +5,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( input: 'pigeons/messages.dart', - swiftOut: 'macos/Classes/messages.g.swift', + swiftOut: + 'darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift', dartOut: 'lib/messages.g.dart', dartTestOut: 'test/messages_test.g.dart', copyrightHeader: 'pigeons/copyright.txt', diff --git a/packages/path_provider/path_provider_foundation/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml index 5a392b03bd9..6410e24fb32 100644 --- a/packages/path_provider/path_provider_foundation/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/pubspec.yaml @@ -2,7 +2,7 @@ name: path_provider_foundation description: iOS and macOS implementation of the path_provider plugin repository: https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.3.2 +version: 2.4.0 environment: sdk: ^3.2.3 diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md index f179d6fbb9c..ce7dd926f6e 100644 --- a/packages/path_provider/path_provider_linux/CHANGELOG.md +++ b/packages/path_provider/path_provider_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.2.1 diff --git a/packages/path_provider/path_provider_linux/README.md b/packages/path_provider/path_provider_linux/README.md index 12c5c51443f..745bbcf7a47 100644 --- a/packages/path_provider/path_provider_linux/README.md +++ b/packages/path_provider/path_provider_linux/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/path_provider -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/path_provider/path_provider_linux/example/pubspec.yaml b/packages/path_provider/path_provider_linux/example/pubspec.yaml index 8a65a40723c..3444e5a1626 100644 --- a/packages/path_provider/path_provider_linux/example/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the path_provider_linux plugin. publish_to: "none" environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml index 3c0c8860dde..b056c30d64d 100644 --- a/packages/path_provider/path_provider_linux/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.2.1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md index 81ee15c3d39..0679b8421cf 100644 --- a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md +++ b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.1.2 diff --git a/packages/path_provider/path_provider_platform_interface/pubspec.yaml b/packages/path_provider/path_provider_platform_interface/pubspec.yaml index 5b6757f0a20..70fb194289b 100644 --- a/packages/path_provider/path_provider_platform_interface/pubspec.yaml +++ b/packages/path_provider/path_provider_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.1.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_windows/CHANGELOG.md b/packages/path_provider/path_provider_windows/CHANGELOG.md index ef77de70bf7..933fe1f1d81 100644 --- a/packages/path_provider/path_provider_windows/CHANGELOG.md +++ b/packages/path_provider/path_provider_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.2.1 diff --git a/packages/path_provider/path_provider_windows/README.md b/packages/path_provider/path_provider_windows/README.md index 2bdeac40993..0aeb40395d8 100644 --- a/packages/path_provider/path_provider_windows/README.md +++ b/packages/path_provider/path_provider_windows/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/path_provider -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/path_provider/path_provider_windows/example/pubspec.yaml b/packages/path_provider/path_provider_windows/example/pubspec.yaml index 39c9d727771..632615bf50d 100644 --- a/packages/path_provider/path_provider_windows/example/pubspec.yaml +++ b/packages/path_provider/path_provider_windows/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the path_provider plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_windows/pubspec.yaml b/packages/path_provider/path_provider_windows/pubspec.yaml index 6549c136c58..c3211cf3c47 100644 --- a/packages/path_provider/path_provider_windows/pubspec.yaml +++ b/packages/path_provider/path_provider_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.2.1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index fb01483089c..e13c7e3c4dd 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,40 @@ +## 20.0.2 + +* [java] Adds `equals` and `hashCode` support for data classes. +* [swift] Fully-qualifies types in Equatable extension test. + +## 20.0.1 + +* [cpp] Fixes handling of null class arguments. + +## 20.0.0 + +* Moves all codec logic to single custom codec per file. +* **Breaking Change** Limits the number of total custom types to 126. + * If more than 126 custom types are needed, consider breaking up your definition files. +* Fixes bug that prevented collection subtypes from being added properly. +* [swift] Adds `@unchecked Sendable` to codec method. +* [objc] [cpp] Fixes bug that prevented setting custom header import path. + +## 19.0.2 + +* [kotlin] Adds the `@JvmOverloads` to the `HostApi` setUp method. This prevents the calling Java code from having to provide an empty `String` as Kotlin provides it by default + +## 19.0.1 + +* [dart] Updates `PigeonInstanceMangerApi` to use the shared api channel code. + +## 19.0.0 + +* **Breaking Change** [swift] Removes `FlutterError` in favor of `PigeonError`. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 18.0.1 + +* Fixes unnecessary calls of `toList` and `fromList` when encoding/decoding data classes. +* [kotlin] Changes to some code to make it more idiomatic. +* Removes collisions with the word `list`. + ## 18.0.0 * Adds message channel suffix option to all APIs. diff --git a/packages/pigeon/CONTRIBUTING.md b/packages/pigeon/CONTRIBUTING.md index 0caf41ec42d..e19f6958623 100644 --- a/packages/pigeon/CONTRIBUTING.md +++ b/packages/pigeon/CONTRIBUTING.md @@ -21,8 +21,10 @@ generators with that AST. * [ast.dart](./lib/ast.dart) - The data structure for representing the Abstract Syntax Tree. * [dart_generator.dart](./lib/dart_generator.dart) - The Dart code generator. * [java_generator.dart](./lib/java_generator.dart) - The Java code generator. +* [kotlin_generator.dart](./lib/kotlin_generator.dart) - The Kotlin code generator. * [objc_generator.dart](./lib/objc_generator.dart) - The Objective-C code generator (header and source files). +* [swift_generator.dart](./lib/swift_generator.dart) - The Swift code generator. * [cpp_generator.dart](./lib/cpp_generator.dart) - The C++ code generator. * [generator_tools.dart](./lib/generator_tools.dart) - Shared code between generators. * [pigeon_cl.dart](./lib/pigeon_cl.dart) - The top-level function executed by @@ -49,6 +51,11 @@ Pigeon has 3 types of tests, you'll find them all in code, then execute the generated code. It can be thought of as unit-tests run against the generated code. Examples: [platform_tests](./platform_tests) +For local testing, always use `test.dart` rather than `run_tests.dart`, as +`run_tests.dart` is specifically a CI entrypoint. When iterating on a specific +generator, you will likely want to use the `-t` flag to specific only the +relevant tests. Pass `-l` to get a list of available tests for the `-t` flag. + ## Generated Source Code Example This is what the temporary generated code that the _PigeonIsolate_ executes diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index dfa4e9847d7..3a46c9707c6 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -21,7 +21,7 @@ Currently pigeon supports generating: ### Supported Datatypes Pigeon uses the `StandardMessageCodec` so it supports -[any datatype platform channels support](https://flutter.dev/docs/development/platform-integration/platform-channels#codec). +[any datatype platform channels support](https://flutter.dev/to/platform-channels-codec). Custom classes, nested datatypes, and enums are also supported. @@ -53,8 +53,7 @@ should be returned via the provided callback. To pass custom details into `PlatformException` for error handling, use `FlutterError` in your Host API. [Example](./example/README.md#HostApi_Example). -To use `FlutterError` in Swift you must first extend a standard error. -[Example](./example/README.md#AppDelegate.swift). +For swift, use `PigeonError` instead of `FlutterError` when throwing an error. See [Example#Swift](./example/README.md#Swift) for more details. #### Objective-C and C++ diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md index 8e22079b47a..46e8c643b1d 100644 --- a/packages/pigeon/example/README.md +++ b/packages/pigeon/example/README.md @@ -115,12 +115,9 @@ Future sendMessage(String messageText) { ### Swift This is the code that will use the generated Swift code to receive calls from Flutter. -packages/pigeon/example/app/ios/Runner/AppDelegate.swift +Unlike other languages, when throwing an error, use `PigeonError` instead of `FlutterError`, as `FlutterError` does not conform to `Swift.Error`. ```swift -// This extension of Error is required to do use FlutterError in any Swift code. -extension FlutterError: Error {} - private class PigeonApiImplementation: ExampleHostApi { func getHostLanguage() throws -> String { return "Swift" @@ -128,14 +125,14 @@ private class PigeonApiImplementation: ExampleHostApi { func add(_ a: Int64, to b: Int64) throws -> Int64 { if a < 0 || b < 0 { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } return a + b } func sendMessage(message: MessageData, completion: @escaping (Result) -> Void) { if message.code == Code.one { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) return } completion(.success(true)) diff --git a/packages/pigeon/example/app/android/.gitignore b/packages/pigeon/example/app/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/pigeon/example/app/android/.gitignore +++ b/packages/pigeon/example/app/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java index 897f40e9bee..8b07206873a 100644 --- a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java +++ b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) @@ -132,6 +133,26 @@ public void setData(@NonNull Map setterArg) { /** Constructor is non-public to enforce null safety; use Builder. */ MessageData() {} + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MessageData that = (MessageData) o; + return Objects.equals(name, that.name) + && Objects.equals(description, that.description) + && code.equals(that.code) + && data.equals(that.data); + } + + @Override + public int hashCode() { + return Objects.hash(name, description, code, data); + } + public static final class Builder { private @Nullable String name; @@ -181,25 +202,57 @@ ArrayList toList() { ArrayList toListResult = new ArrayList(4); toListResult.add(name); toListResult.add(description); - toListResult.add(code == null ? null : code.index); + toListResult.add(code); toListResult.add(data); return toListResult; } - static @NonNull MessageData fromList(@NonNull ArrayList list) { + static @NonNull MessageData fromList(@NonNull ArrayList __pigeon_list) { MessageData pigeonResult = new MessageData(); - Object name = list.get(0); + Object name = __pigeon_list.get(0); pigeonResult.setName((String) name); - Object description = list.get(1); + Object description = __pigeon_list.get(1); pigeonResult.setDescription((String) description); - Object code = list.get(2); - pigeonResult.setCode(Code.values()[(int) code]); - Object data = list.get(3); + Object code = __pigeon_list.get(2); + pigeonResult.setCode((Code) code); + Object data = __pigeon_list.get(3); pigeonResult.setData((Map) data); return pigeonResult; } } + private static class PigeonCodec extends StandardMessageCodec { + public static final PigeonCodec INSTANCE = new PigeonCodec(); + + private PigeonCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 129: + return MessageData.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + Object value = readValue(buffer); + return value == null ? null : Code.values()[(int) value]; + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof MessageData) { + stream.write(129); + writeValue(stream, ((MessageData) value).toList()); + } else if (value instanceof Code) { + stream.write(130); + writeValue(stream, value == null ? null : ((Code) value).index); + } else { + super.writeValue(stream, value); + } + } + } + /** Asynchronous error handling return type for non-nullable API method returns. */ public interface Result { /** Success case callback method for handling returns. */ @@ -224,33 +277,6 @@ public interface VoidResult { /** Failure case callback method for handling errors. */ void error(@NonNull Throwable error); } - - private static class ExampleHostApiCodec extends StandardMessageCodec { - public static final ExampleHostApiCodec INSTANCE = new ExampleHostApiCodec(); - - private ExampleHostApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return MessageData.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof MessageData) { - stream.write(128); - writeValue(stream, ((MessageData) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface ExampleHostApi { @@ -264,7 +290,7 @@ public interface ExampleHostApi { /** The codec used by ExampleHostApi. */ static @NonNull MessageCodec getCodec() { - return ExampleHostApiCodec.INSTANCE; + return PigeonCodec.INSTANCE; } /** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable ExampleHostApi api) { @@ -382,7 +408,7 @@ public MessageFlutterApi( /** Public interface for sending reply. */ /** The codec used by MessageFlutterApi. */ static @NonNull MessageCodec getCodec() { - return new StandardMessageCodec(); + return PigeonCodec.INSTANCE; } public void flutterMethod(@Nullable String aStringArg, @NonNull Result result) { diff --git a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt index 350895738d6..6542e190e78 100644 --- a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt +++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt @@ -3,6 +3,7 @@ // found in the LICENSE file. // Autogenerated from Pigeon, do not edit directly. // See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") import android.util.Log import io.flutter.plugin.common.BasicMessageChannel @@ -17,10 +18,10 @@ private fun wrapResult(result: Any?): List { } private fun wrapError(exception: Throwable): List { - if (exception is FlutterError) { - return listOf(exception.code, exception.message, exception.details) + return if (exception is FlutterError) { + listOf(exception.code, exception.message, exception.details) } else { - return listOf( + listOf( exception.javaClass.simpleName, exception.toString(), "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) @@ -64,33 +65,35 @@ data class MessageData( val data: Map ) { companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): MessageData { - val name = list[0] as String? - val description = list[1] as String? - val code = Code.ofRaw(list[2] as Int)!! - val data = list[3] as Map + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): MessageData { + val name = __pigeon_list[0] as String? + val description = __pigeon_list[1] as String? + val code = __pigeon_list[2] as Code + val data = __pigeon_list[3] as Map return MessageData(name, description, code, data) } } fun toList(): List { - return listOf( + return listOf( name, description, - code.raw, + code, data, ) } } -@Suppress("UNCHECKED_CAST") -private object ExampleHostApiCodec : StandardMessageCodec() { +private object MessagesPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { - 128.toByte() -> { + 129.toByte() -> { return (readValue(buffer) as? List)?.let { MessageData.fromList(it) } } + 130.toByte() -> { + return (readValue(buffer) as Int?)?.let { Code.ofRaw(it) } + } else -> super.readValueOfType(type, buffer) } } @@ -98,9 +101,13 @@ private object ExampleHostApiCodec : StandardMessageCodec() { override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { when (value) { is MessageData -> { - stream.write(128) + stream.write(129) writeValue(stream, value.toList()) } + is Code -> { + stream.write(130) + writeValue(stream, value.raw) + } else -> super.writeValue(stream, value) } } @@ -116,9 +123,9 @@ interface ExampleHostApi { companion object { /** The codec used by ExampleHostApi. */ - val codec: MessageCodec by lazy { ExampleHostApiCodec } + val codec: MessageCodec by lazy { MessagesPigeonCodec } /** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ - @Suppress("UNCHECKED_CAST") + @JvmOverloads fun setUp( binaryMessenger: BinaryMessenger, api: ExampleHostApi?, @@ -134,12 +141,12 @@ interface ExampleHostApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - var wrapped: List - try { - wrapped = listOf(api.getHostLanguage()) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.getHostLanguage()) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -155,14 +162,14 @@ interface ExampleHostApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aArg = args[0].let { if (it is Int) it.toLong() else it as Long } - val bArg = args[1].let { if (it is Int) it.toLong() else it as Long } - var wrapped: List - try { - wrapped = listOf(api.add(aArg, bArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val aArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val bArg = args[1].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + listOf(api.add(aArg, bArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -197,14 +204,13 @@ interface ExampleHostApi { } } /** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ -@Suppress("UNCHECKED_CAST") class MessageFlutterApi( private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "" ) { companion object { /** The codec used by MessageFlutterApi. */ - val codec: MessageCodec by lazy { StandardMessageCodec() } + val codec: MessageCodec by lazy { MessagesPigeonCodec } } fun flutterMethod(aStringArg: String?, callback: (Result) -> Unit) { diff --git a/packages/pigeon/example/app/android/build.gradle b/packages/pigeon/example/app/android/build.gradle index 39a10a52cbe..fdf447f8a7e 100644 --- a/packages/pigeon/example/app/android/build.gradle +++ b/packages/pigeon/example/app/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/pigeon/example/app/android/settings.gradle b/packages/pigeon/example/app/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/pigeon/example/app/android/settings.gradle +++ b/packages/pigeon/example/app/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift index f259b759085..51119e23fa3 100644 --- a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift +++ b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift @@ -6,9 +6,6 @@ import Flutter import UIKit // #docregion swift-class -// This extension of Error is required to do use FlutterError in any Swift code. -extension FlutterError: Error {} - private class PigeonApiImplementation: ExampleHostApi { func getHostLanguage() throws -> String { return "Swift" @@ -16,14 +13,14 @@ private class PigeonApiImplementation: ExampleHostApi { func add(_ a: Int64, to b: Int64) throws -> Int64 { if a < 0 || b < 0 { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } return a + b } func sendMessage(message: MessageData, completion: @escaping (Result) -> Void) { if message.code == Code.one { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) return } completion(.success(true)) diff --git a/packages/pigeon/example/app/ios/Runner/Messages.g.swift b/packages/pigeon/example/app/ios/Runner/Messages.g.swift index 313c1974e02..3d5362cc4f1 100644 --- a/packages/pigeon/example/app/ios/Runner/Messages.g.swift +++ b/packages/pigeon/example/app/ios/Runner/Messages.g.swift @@ -14,11 +14,36 @@ import Foundation #error("Unsupported platform.") #endif +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + private func wrapResult(_ result: Any?) -> [Any?] { return [result] } private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } if let flutterError = error as? FlutterError { return [ flutterError.code, @@ -33,8 +58,8 @@ private func wrapError(_ error: Any) -> [Any?] { ] } -private func createConnectionError(withChannelName channelName: String) -> FlutterError { - return FlutterError( +private func createConnectionError(withChannelName channelName: String) -> PigeonError { + return PigeonError( code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") } @@ -60,11 +85,12 @@ struct MessageData { var code: Code var data: [String?: String?] - static func fromList(_ list: [Any?]) -> MessageData? { - let name: String? = nilOrValue(list[0]) - let description: String? = nilOrValue(list[1]) - let code = Code(rawValue: list[2] as! Int)! - let data = list[3] as! [String?: String?] + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> MessageData? { + let name: String? = nilOrValue(__pigeon_list[0]) + let description: String? = nilOrValue(__pigeon_list[1]) + let code = __pigeon_list[2] as! Code + let data = __pigeon_list[3] as! [String?: String?] return MessageData( name: name, @@ -77,46 +103,55 @@ struct MessageData { return [ name, description, - code.rawValue, + code, data, ] } } - -private class ExampleHostApiCodecReader: FlutterStandardReader { +private class MessagesPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { - case 128: + case 129: return MessageData.fromList(self.readValue() as! [Any?]) + case 130: + var enumResult: Code? = nil + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + enumResult = Code(rawValue: enumResultAsInt) + } + return enumResult default: return super.readValue(ofType: type) } } } -private class ExampleHostApiCodecWriter: FlutterStandardWriter { +private class MessagesPigeonCodecWriter: FlutterStandardWriter { override func writeValue(_ value: Any) { if let value = value as? MessageData { - super.writeByte(128) + super.writeByte(129) super.writeValue(value.toList()) + } else if let value = value as? Code { + super.writeByte(130) + super.writeValue(value.rawValue) } else { super.writeValue(value) } } } -private class ExampleHostApiCodecReaderWriter: FlutterStandardReaderWriter { +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { override func reader(with data: Data) -> FlutterStandardReader { - return ExampleHostApiCodecReader(data: data) + return MessagesPigeonCodecReader(data: data) } override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return ExampleHostApiCodecWriter(data: data) + return MessagesPigeonCodecWriter(data: data) } } -class ExampleHostApiCodec: FlutterStandardMessageCodec { - static let shared = ExampleHostApiCodec(readerWriter: ExampleHostApiCodecReaderWriter()) +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) } /// Generated protocol from Pigeon that represents a handler of messages from Flutter. @@ -128,8 +163,7 @@ protocol ExampleHostApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class ExampleHostApiSetup { - /// The codec used by ExampleHostApi. - static var codec: FlutterStandardMessageCodec { ExampleHostApiCodec.shared } + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } /// Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?, messageChannelSuffix: String = "" @@ -193,7 +227,7 @@ class ExampleHostApiSetup { /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol MessageFlutterApiProtocol { func flutterMethod( - aString aStringArg: String?, completion: @escaping (Result) -> Void) + aString aStringArg: String?, completion: @escaping (Result) -> Void) } class MessageFlutterApi: MessageFlutterApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -202,12 +236,16 @@ class MessageFlutterApi: MessageFlutterApiProtocol { self.binaryMessenger = binaryMessenger self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" } + var codec: MessagesPigeonCodec { + return MessagesPigeonCodec.shared + } func flutterMethod( - aString aStringArg: String?, completion: @escaping (Result) -> Void + aString aStringArg: String?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod\(messageChannelSuffix)" - let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger) + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([aStringArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) @@ -217,11 +255,11 @@ class MessageFlutterApi: MessageFlutterApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { diff --git a/packages/pigeon/example/app/lib/src/messages.g.dart b/packages/pigeon/example/app/lib/src/messages.g.dart index 071711ffaa9..d8d4fde551c 100644 --- a/packages/pigeon/example/app/lib/src/messages.g.dart +++ b/packages/pigeon/example/app/lib/src/messages.g.dart @@ -54,7 +54,7 @@ class MessageData { return [ name, description, - code.index, + code, data, ]; } @@ -64,19 +64,22 @@ class MessageData { return MessageData( name: result[0] as String?, description: result[1] as String?, - code: Code.values[result[2]! as int], + code: result[2]! as Code, data: (result[3] as Map?)!.cast(), ); } } -class _ExampleHostApiCodec extends StandardMessageCodec { - const _ExampleHostApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is MessageData) { - buffer.putUint8(128); + buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is Code) { + buffer.putUint8(130); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -85,8 +88,11 @@ class _ExampleHostApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 129: return MessageData.decode(readValue(buffer)!); + case 130: + final int? value = readValue(buffer) as int?; + return value == null ? null : Code.values[value]; default: return super.readValueOfType(type, buffer); } @@ -104,8 +110,7 @@ class ExampleHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - _ExampleHostApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -198,8 +203,7 @@ class ExampleHostApi { } abstract class MessageFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); String flutterMethod(String? aString); diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h index db20dcba17e..8a51885ec9f 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.h +++ b/packages/pigeon/example/app/macos/Runner/messages.g.h @@ -39,8 +39,8 @@ typedef NS_ENUM(NSUInteger, PGNCode) { @property(nonatomic, copy) NSDictionary *data; @end -/// The codec used by PGNExampleHostApi. -NSObject *PGNExampleHostApiGetCodec(void); +/// The codec used by all APIs. +NSObject *PGNGetMessagesCodec(void); @protocol PGNExampleHostApi /// @return `nil` only when `error != nil`. @@ -60,9 +60,6 @@ extern void SetUpPGNExampleHostApiWithSuffix(id binaryMe NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by PGNMessageFlutterApi. -NSObject *PGNMessageFlutterApiGetCodec(void); - @interface PGNMessageFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.m b/packages/pigeon/example/app/macos/Runner/messages.g.m index 90bd065401c..1471345b880 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.m +++ b/packages/pigeon/example/app/macos/Runner/messages.g.m @@ -16,7 +16,7 @@ #error File requires ARC to be enabled. #endif -static NSArray *wrapResult(id result, FlutterError *error) { +static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] @@ -34,7 +34,7 @@ details:@""]; } -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @@ -50,9 +50,9 @@ - (instancetype)initWithValue:(PGNCode)value { @end @interface PGNMessageData () -+ (PGNMessageData *)fromList:(NSArray *)list; -+ (nullable PGNMessageData *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (PGNMessageData *)fromList:(NSArray *)list; ++ (nullable PGNMessageData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @implementation PGNMessageData @@ -67,75 +67,84 @@ + (instancetype)makeWithName:(nullable NSString *)name pigeonResult.data = data; return pigeonResult; } -+ (PGNMessageData *)fromList:(NSArray *)list { ++ (PGNMessageData *)fromList:(NSArray *)list { PGNMessageData *pigeonResult = [[PGNMessageData alloc] init]; pigeonResult.name = GetNullableObjectAtIndex(list, 0); pigeonResult.description = GetNullableObjectAtIndex(list, 1); - pigeonResult.code = [GetNullableObjectAtIndex(list, 2) integerValue]; + PGNCodeBox *enumBox = GetNullableObjectAtIndex(list, 2); + pigeonResult.code = enumBox.value; pigeonResult.data = GetNullableObjectAtIndex(list, 3); return pigeonResult; } -+ (nullable PGNMessageData *)nullableFromList:(NSArray *)list { ++ (nullable PGNMessageData *)nullableFromList:(NSArray *)list { return (list) ? [PGNMessageData fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.name ?: [NSNull null], self.description ?: [NSNull null], - @(self.code), + [[PGNCodeBox alloc] initWithValue:self.code], self.data ?: [NSNull null], ]; } @end -@interface PGNExampleHostApiCodecReader : FlutterStandardReader +@interface PGNMessagesPigeonCodecReader : FlutterStandardReader @end -@implementation PGNExampleHostApiCodecReader +@implementation PGNMessagesPigeonCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { - case 128: + case 129: return [PGNMessageData fromList:[self readValue]]; + case 130: { + NSNumber *enumAsNumber = [self readValue]; + return enumAsNumber == nil ? nil + : [[PGNCodeBox alloc] initWithValue:[enumAsNumber integerValue]]; + } default: return [super readValueOfType:type]; } } @end -@interface PGNExampleHostApiCodecWriter : FlutterStandardWriter +@interface PGNMessagesPigeonCodecWriter : FlutterStandardWriter @end -@implementation PGNExampleHostApiCodecWriter +@implementation PGNMessagesPigeonCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[PGNMessageData class]]) { - [self writeByte:128]; + [self writeByte:129]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[PGNCodeBox class]]) { + PGNCodeBox *box = (PGNCodeBox *)value; + [self writeByte:130]; + [self writeValue:(value == nil ? [NSNull null] : [NSNumber numberWithInteger:box.value])]; } else { [super writeValue:value]; } } @end -@interface PGNExampleHostApiCodecReaderWriter : FlutterStandardReaderWriter +@interface PGNMessagesPigeonCodecReaderWriter : FlutterStandardReaderWriter @end -@implementation PGNExampleHostApiCodecReaderWriter +@implementation PGNMessagesPigeonCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[PGNExampleHostApiCodecWriter alloc] initWithData:data]; + return [[PGNMessagesPigeonCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[PGNExampleHostApiCodecReader alloc] initWithData:data]; + return [[PGNMessagesPigeonCodecReader alloc] initWithData:data]; } @end -NSObject *PGNExampleHostApiGetCodec(void) { +NSObject *PGNGetMessagesCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - PGNExampleHostApiCodecReaderWriter *readerWriter = - [[PGNExampleHostApiCodecReaderWriter alloc] init]; + PGNMessagesPigeonCodecReaderWriter *readerWriter = + [[PGNMessagesPigeonCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } - void SetUpPGNExampleHostApi(id binaryMessenger, NSObject *api) { SetUpPGNExampleHostApiWithSuffix(binaryMessenger, api, @""); @@ -154,7 +163,7 @@ void SetUpPGNExampleHostApiWithSuffix(id binaryMessenger @"ExampleHostApi.getHostLanguage", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:PGNExampleHostApiGetCodec()]; + codec:PGNGetMessagesCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(getHostLanguageWithError:)], @@ -177,14 +186,14 @@ void SetUpPGNExampleHostApiWithSuffix(id binaryMessenger @"dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:PGNExampleHostApiGetCodec()]; + codec:PGNGetMessagesCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(addNumber:toNumber:error:)], @"PGNExampleHostApi api (%@) doesn't respond to @selector(addNumber:toNumber:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_a = [GetNullableObjectAtIndex(args, 0) integerValue]; NSInteger arg_b = [GetNullableObjectAtIndex(args, 1) integerValue]; FlutterError *error; @@ -202,14 +211,14 @@ void SetUpPGNExampleHostApiWithSuffix(id binaryMessenger @"ExampleHostApi.sendMessage", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:PGNExampleHostApiGetCodec()]; + codec:PGNGetMessagesCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(sendMessageMessage:completion:)], @"PGNExampleHostApi api (%@) doesn't respond to " @"@selector(sendMessageMessage:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; PGNMessageData *arg_message = GetNullableObjectAtIndex(args, 0); [api sendMessageMessage:arg_message completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -221,12 +230,6 @@ void SetUpPGNExampleHostApiWithSuffix(id binaryMessenger } } } -NSObject *PGNMessageFlutterApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - sSharedObject = [FlutterStandardMessageCodec sharedInstance]; - return sSharedObject; -} - @interface PGNMessageFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @property(nonatomic, strong) NSString *messageChannelSuffix; @@ -257,7 +260,7 @@ - (void)flutterMethodAString:(nullable NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:PGNMessageFlutterApiGetCodec()]; + codec:PGNGetMessagesCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { diff --git a/packages/pigeon/example/app/pubspec.yaml b/packages/pigeon/example/app/pubspec.yaml index ef237fb9bdd..e0cb64f60a7 100644 --- a/packages/pigeon/example/app/pubspec.yaml +++ b/packages/pigeon/example/app/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: flutter: diff --git a/packages/pigeon/example/app/windows/runner/messages.g.cpp b/packages/pigeon/example/app/windows/runner/messages.g.cpp index 5f6e25a18db..7a43ec397a3 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.cpp +++ b/packages/pigeon/example/app/windows/runner/messages.g.cpp @@ -82,14 +82,15 @@ EncodableList MessageData::ToEncodableList() const { list.push_back(name_ ? EncodableValue(*name_) : EncodableValue()); list.push_back(description_ ? EncodableValue(*description_) : EncodableValue()); - list.push_back(EncodableValue((int)code_)); + list.push_back(CustomEncodableValue(code_)); list.push_back(EncodableValue(data_)); return list; } MessageData MessageData::FromEncodableList(const EncodableList& list) { - MessageData decoded((Code)(std::get(list[2])), - std::get(list[3])); + MessageData decoded( + std::any_cast(std::get(list[2])), + std::get(list[3])); auto& encodable_name = list[0]; if (!encodable_name.IsNull()) { decoded.set_name(std::get(encodable_name)); @@ -101,31 +102,46 @@ MessageData MessageData::FromEncodableList(const EncodableList& list) { return decoded; } -ExampleHostApiCodecSerializer::ExampleHostApiCodecSerializer() {} +PigeonCodecSerializer::PigeonCodecSerializer() {} -EncodableValue ExampleHostApiCodecSerializer::ReadValueOfType( +EncodableValue PigeonCodecSerializer::ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const { switch (type) { - case 128: + case 129: return CustomEncodableValue(MessageData::FromEncodableList( std::get(ReadValue(stream)))); + case 130: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = + encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() + ? EncodableValue() + : CustomEncodableValue(static_cast(enum_arg_value)); + } default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } } -void ExampleHostApiCodecSerializer::WriteValue( +void PigeonCodecSerializer::WriteValue( const EncodableValue& value, flutter::ByteStreamWriter* stream) const { if (const CustomEncodableValue* custom_value = std::get_if(&value)) { if (custom_value->type() == typeid(MessageData)) { - stream->WriteByte(128); + stream->WriteByte(129); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), stream); return; } + if (custom_value->type() == typeid(Code)) { + stream->WriteByte(130); + WriteValue( + EncodableValue(static_cast(std::any_cast(*custom_value))), + stream); + return; + } } flutter::StandardCodecSerializer::WriteValue(value, stream); } @@ -133,7 +149,7 @@ void ExampleHostApiCodecSerializer::WriteValue( /// The codec used by ExampleHostApi. const flutter::StandardMessageCodec& ExampleHostApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &ExampleHostApiCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } // Sets up an instance of `ExampleHostApi` to handle messages through the @@ -282,7 +298,7 @@ MessageFlutterApi::MessageFlutterApi(flutter::BinaryMessenger* binary_messenger, const flutter::StandardMessageCodec& MessageFlutterApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &flutter::StandardCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } void MessageFlutterApi::FlutterMethod( diff --git a/packages/pigeon/example/app/windows/runner/messages.g.h b/packages/pigeon/example/app/windows/runner/messages.g.h index 1b9ca3626cb..84368cbb1df 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.h +++ b/packages/pigeon/example/app/windows/runner/messages.g.h @@ -89,20 +89,19 @@ class MessageData { static MessageData FromEncodableList(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class ExampleHostApi; - friend class ExampleHostApiCodecSerializer; friend class MessageFlutterApi; - friend class MessageFlutterApiCodecSerializer; + friend class PigeonCodecSerializer; std::optional name_; std::optional description_; Code code_; flutter::EncodableMap data_; }; -class ExampleHostApiCodecSerializer : public flutter::StandardCodecSerializer { +class PigeonCodecSerializer : public flutter::StandardCodecSerializer { public: - ExampleHostApiCodecSerializer(); - inline static ExampleHostApiCodecSerializer& GetInstance() { - static ExampleHostApiCodecSerializer sInstance; + PigeonCodecSerializer(); + inline static PigeonCodecSerializer& GetInstance() { + static PigeonCodecSerializer sInstance; return sInstance; } diff --git a/packages/pigeon/example/pubspec.yaml b/packages/pigeon/example/pubspec.yaml index b8b67ac2d9e..63663a48ec2 100644 --- a/packages/pigeon/example/pubspec.yaml +++ b/packages/pigeon/example/pubspec.yaml @@ -3,7 +3,7 @@ description: example app to show basic usage of pigeon. publish_to: none environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 08eef8cc6c7..b3469b03295 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -515,10 +515,22 @@ class TypeDeclaration { ); } + /// Returns duplicated `TypeDeclaration` with attached `associatedProxyApi` value. + TypeDeclaration copyWithTypeArguments(List types) { + return TypeDeclaration( + baseName: baseName, + isNullable: isNullable, + typeArguments: types, + associatedClass: associatedClass, + associatedEnum: associatedEnum, + associatedProxyApi: associatedProxyApi, + ); + } + @override String toString() { final String typeArgumentsStr = - typeArguments.isEmpty ? '' : 'typeArguments:$typeArguments'; + typeArguments.isEmpty ? '' : ' typeArguments:$typeArguments'; return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass isProxyApi:$isProxyApi)'; } } diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index 92d6fd56d1f..47dd2b3276b 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -18,7 +18,10 @@ const DocumentCommentSpecification _docCommentSpec = DocumentCommentSpecification(_commentPrefix); /// The default serializer for Flutter. -const String _defaultCodecSerializer = 'flutter::StandardCodecSerializer'; +const String _standardCodecSerializer = 'flutter::StandardCodecSerializer'; + +/// The name of the codec serializer. +const String _codecSerializerName = 'PigeonCodecSerializer'; /// Options that control how C++ code will be generated. class CppOptions { @@ -47,7 +50,7 @@ class CppOptions { /// `x = CppOptions.fromMap(x.toMap())`. static CppOptions fromMap(Map map) { return CppOptions( - headerIncludePath: map['header'] as String?, + headerIncludePath: map['headerIncludePath'] as String?, namespace: map['namespace'] as String?, copyrightHeader: map['copyrightHeader'] as Iterable?, headerOutPath: map['cppHeaderOut'] as String?, @@ -58,7 +61,7 @@ class CppOptions { /// `x = CppOptions.fromMap(x.toMap())`. Map toMap() { final Map result = { - if (headerIncludePath != null) 'header': headerIncludePath!, + if (headerIncludePath != null) 'headerIncludePath': headerIncludePath!, if (namespace != null) 'namespace': namespace!, if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!, }; @@ -332,8 +335,8 @@ class CppHeaderGenerator extends StructuredGenerator { // TODO(gaaclarke): Find a way to be more precise with our // friendships. indent.writeln('friend class ${api.name};'); - indent.writeln('friend class ${_getCodecSerializerName(api)};'); } + indent.writeln('friend class $_codecSerializerName;'); if (testFixtureClass != null) { indent.writeln('friend class $testFixtureClass;'); } @@ -349,6 +352,49 @@ class CppHeaderGenerator extends StructuredGenerator { indent.newln(); } + @override + void writeGeneralCodec( + CppOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + indent.write( + 'class $_codecSerializerName : public $_standardCodecSerializer '); + indent.addScoped('{', '};', () { + _writeAccessBlock(indent, _ClassAccess.public, () { + _writeFunctionDeclaration(indent, _codecSerializerName, + isConstructor: true); + _writeFunctionDeclaration(indent, 'GetInstance', + returnType: '$_codecSerializerName&', + isStatic: true, inlineBody: () { + indent.writeln('static $_codecSerializerName sInstance;'); + indent.writeln('return sInstance;'); + }); + indent.newln(); + _writeFunctionDeclaration(indent, 'WriteValue', + returnType: _voidType, + parameters: [ + 'const flutter::EncodableValue& value', + 'flutter::ByteStreamWriter* stream' + ], + isConst: true, + isOverride: true); + }); + indent.writeScoped(' protected:', '', () { + _writeFunctionDeclaration(indent, 'ReadValueOfType', + returnType: 'flutter::EncodableValue', + parameters: [ + 'uint8_t type', + 'flutter::ByteStreamReader* stream' + ], + isConst: true, + isOverride: true); + }); + }, nestCount: 0); + indent.newln(); + } + @override void writeFlutterApi( CppOptions generatorOptions, @@ -357,9 +403,6 @@ class CppHeaderGenerator extends StructuredGenerator { AstFlutterApi api, { required String dartPackageName, }) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(generatorOptions, root, indent, api); - } const List generatedMessages = [ ' Generated class from Pigeon that represents Flutter messages that can be called from C++.' ]; @@ -415,9 +458,6 @@ class CppHeaderGenerator extends StructuredGenerator { AstHostApi api, { required String dartPackageName, }) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(generatorOptions, root, indent, api); - } const List generatedMessages = [ ' Generated interface from Pigeon that represents a handler of messages from Flutter.' ]; @@ -521,45 +561,6 @@ class CppHeaderGenerator extends StructuredGenerator { indent.newln(); } - void _writeCodec( - CppOptions generatorOptions, Root root, Indent indent, Api api) { - assert(getCodecClasses(api, root).isNotEmpty); - final String codeSerializerName = _getCodecSerializerName(api); - indent - .write('class $codeSerializerName : public $_defaultCodecSerializer '); - indent.addScoped('{', '};', () { - _writeAccessBlock(indent, _ClassAccess.public, () { - _writeFunctionDeclaration(indent, codeSerializerName, - isConstructor: true); - _writeFunctionDeclaration(indent, 'GetInstance', - returnType: '$codeSerializerName&', isStatic: true, inlineBody: () { - indent.writeln('static $codeSerializerName sInstance;'); - indent.writeln('return sInstance;'); - }); - indent.newln(); - _writeFunctionDeclaration(indent, 'WriteValue', - returnType: _voidType, - parameters: [ - 'const flutter::EncodableValue& value', - 'flutter::ByteStreamWriter* stream' - ], - isConst: true, - isOverride: true); - }); - indent.writeScoped(' protected:', '', () { - _writeFunctionDeclaration(indent, 'ReadValueOfType', - returnType: 'flutter::EncodableValue', - parameters: [ - 'uint8_t type', - 'flutter::ByteStreamReader* stream' - ], - isConst: true, - isOverride: true); - }); - }, nestCount: 0); - indent.newln(); - } - void _writeFlutterError(Indent indent) { indent.format(''' @@ -787,8 +788,12 @@ class CppSourceGenerator extends StructuredGenerator { final HostDatatype hostDatatype = getFieldHostDatatype(field, _shortBaseCppTypeForBuiltinDartType); final String encodableValue = _wrappedHostApiArgumentExpression( - root, _makeInstanceVariableName(field), field.type, hostDatatype, - preSerializeClasses: true); + root, + _makeInstanceVariableName(field), + field.type, + hostDatatype, + true, + ); indent.writeln('list.push_back($encodableValue);'); } indent.writeln('return list;'); @@ -806,20 +811,15 @@ class CppSourceGenerator extends StructuredGenerator { // Returns the expression to convert the given EncodableValue to a field // value. String getValueExpression(NamedType field, String encodable) { - if (field.type.isEnum) { - return '(${field.type.baseName})(std::get($encodable))'; - } else if (field.type.baseName == 'int') { + if (field.type.baseName == 'int') { return '$encodable.LongValue()'; } else if (field.type.baseName == 'Object') { return encodable; } else { final HostDatatype hostDatatype = getFieldHostDatatype(field, _shortBaseCppTypeForBuiltinDartType); - if (!hostDatatype.isBuiltin && - root.classes - .map((Class x) => x.name) - .contains(field.type.baseName)) { - return '${hostDatatype.datatype}::FromEncodableList(std::get($encodable))'; + if (field.type.isClass || field.type.isEnum) { + return _classReferenceFromEncodableValue(hostDatatype, encodable); } else { return 'std::get<${hostDatatype.datatype}>($encodable)'; } @@ -874,6 +874,90 @@ class CppSourceGenerator extends StructuredGenerator { }); } + @override + void writeGeneralCodec( + CppOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + final Iterable customTypes = getEnumeratedTypes(root); + indent.newln(); + _writeFunctionDefinition(indent, _codecSerializerName, + scope: _codecSerializerName); + _writeFunctionDefinition(indent, 'ReadValueOfType', + scope: _codecSerializerName, + returnType: 'EncodableValue', + parameters: [ + 'uint8_t type', + 'flutter::ByteStreamReader* stream', + ], + isConst: true, body: () { + if (customTypes.isNotEmpty) { + indent.writeln('switch (type) {'); + indent.inc(); + for (final EnumeratedType customType in customTypes) { + indent.writeln('case ${customType.enumeration}:'); + indent.nest(1, () { + if (customType.type == CustomTypes.customClass) { + indent.writeln( + 'return CustomEncodableValue(${customType.name}::FromEncodableList(std::get(ReadValue(stream))));'); + } else if (customType.type == CustomTypes.customEnum) { + indent.writeScoped('{', '}', () { + indent.writeln( + 'const auto& encodable_enum_arg = ReadValue(stream);'); + indent.writeln( + 'const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue();'); + indent.writeln( + 'return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast<${customType.name}>(enum_arg_value));'); + }); + } + }); + } + indent.writeln('default:'); + indent.inc(); + } + indent.writeln( + 'return $_standardCodecSerializer::ReadValueOfType(type, stream);'); + if (customTypes.isNotEmpty) { + indent.dec(); + indent.writeln('}'); + indent.dec(); + } + }); + _writeFunctionDefinition(indent, 'WriteValue', + scope: _codecSerializerName, + returnType: _voidType, + parameters: [ + 'const EncodableValue& value', + 'flutter::ByteStreamWriter* stream', + ], + isConst: true, body: () { + if (customTypes.isNotEmpty) { + indent.write( + 'if (const CustomEncodableValue* custom_value = std::get_if(&value)) '); + indent.addScoped('{', '}', () { + for (final EnumeratedType customType in customTypes) { + indent.write( + 'if (custom_value->type() == typeid(${customType.name})) '); + indent.addScoped('{', '}', () { + indent.writeln('stream->WriteByte(${customType.enumeration});'); + if (customType.type == CustomTypes.customClass) { + indent.writeln( + 'WriteValue(EncodableValue(std::any_cast<${customType.name}>(*custom_value).ToEncodableList()), stream);'); + } else if (customType.type == CustomTypes.customEnum) { + indent.writeln( + 'WriteValue(EncodableValue(static_cast(std::any_cast<${customType.name}>(*custom_value))), stream);'); + } + indent.writeln('return;'); + }); + } + }); + } + indent.writeln('$_standardCodecSerializer::WriteValue(value, stream);'); + }); + } + @override void writeFlutterApi( CppOptions generatorOptions, @@ -882,9 +966,6 @@ class CppSourceGenerator extends StructuredGenerator { AstFlutterApi api, { required String dartPackageName, }) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(generatorOptions, root, indent, api); - } indent.writeln( '$_commentPrefix Generated class from Pigeon that represents Flutter messages that can be called from C++.'); _writeFunctionDefinition( @@ -912,9 +993,6 @@ class CppSourceGenerator extends StructuredGenerator { 'message_channel_suffix_(message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : "")' ], ); - final String codeSerializerName = getCodecClasses(api, root).isNotEmpty - ? _getCodecSerializerName(api) - : _defaultCodecSerializer; _writeFunctionDefinition( indent, 'GetCodec', @@ -922,7 +1000,7 @@ class CppSourceGenerator extends StructuredGenerator { returnType: 'const flutter::StandardMessageCodec&', body: () { indent.writeln( - 'return flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance());'); + 'return flutter::StandardMessageCodec::GetInstance(&$_codecSerializerName::GetInstance());'); }, ); for (final Method func in api.methods) { @@ -960,8 +1038,12 @@ class CppSourceGenerator extends StructuredGenerator { indent.addScoped('EncodableValue(EncodableList{', '});', () { for (final _HostNamedType param in hostParameters) { final String encodedArgument = _wrappedHostApiArgumentExpression( - root, param.name, param.originalType, param.hostType, - preSerializeClasses: false); + root, + param.name, + param.originalType, + param.hostType, + false, + ); indent.writeln('$encodedArgument,'); } }); @@ -1018,19 +1100,12 @@ class CppSourceGenerator extends StructuredGenerator { AstHostApi api, { required String dartPackageName, }) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(generatorOptions, root, indent, api); - } - - final String codeSerializerName = getCodecClasses(api, root).isNotEmpty - ? _getCodecSerializerName(api) - : _defaultCodecSerializer; indent.writeln('/// The codec used by ${api.name}.'); _writeFunctionDefinition(indent, 'GetCodec', scope: api.name, returnType: 'const flutter::StandardMessageCodec&', body: () { indent.writeln( - 'return flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance());'); + 'return flutter::StandardMessageCodec::GetInstance(&$_codecSerializerName::GetInstance());'); }); indent.writeln( '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); @@ -1172,67 +1247,6 @@ return EncodableValue(EncodableList{ }); } - void _writeCodec( - CppOptions generatorOptions, - Root root, - Indent indent, - Api api, - ) { - assert(getCodecClasses(api, root).isNotEmpty); - final String codeSerializerName = _getCodecSerializerName(api); - indent.newln(); - _writeFunctionDefinition(indent, codeSerializerName, - scope: codeSerializerName); - _writeFunctionDefinition(indent, 'ReadValueOfType', - scope: codeSerializerName, - returnType: 'EncodableValue', - parameters: [ - 'uint8_t type', - 'flutter::ByteStreamReader* stream', - ], - isConst: true, body: () { - indent.write('switch (type) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in getCodecClasses(api, root)) { - indent.writeln('case ${customClass.enumeration}:'); - indent.nest(1, () { - indent.writeln( - 'return CustomEncodableValue(${customClass.name}::FromEncodableList(std::get(ReadValue(stream))));'); - }); - } - indent.writeln('default:'); - indent.nest(1, () { - indent.writeln( - 'return $_defaultCodecSerializer::ReadValueOfType(type, stream);'); - }); - }); - }); - _writeFunctionDefinition(indent, 'WriteValue', - scope: codeSerializerName, - returnType: _voidType, - parameters: [ - 'const EncodableValue& value', - 'flutter::ByteStreamWriter* stream', - ], - isConst: true, body: () { - indent.write( - 'if (const CustomEncodableValue* custom_value = std::get_if(&value)) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in getCodecClasses(api, root)) { - indent.write( - 'if (custom_value->type() == typeid(${customClass.name})) '); - indent.addScoped('{', '}', () { - indent.writeln('stream->WriteByte(${customClass.enumeration});'); - indent.writeln( - 'WriteValue(EncodableValue(std::any_cast<${customClass.name}>(*custom_value).ToEncodableList()), stream);'); - indent.writeln('return;'); - }); - } - }); - indent.writeln('$_defaultCodecSerializer::WriteValue(value, stream);'); - }); - } - void _writeClassConstructor(Root root, Indent indent, Class classDefinition, Iterable params) { final Iterable<_HostNamedType> hostParams = params.map((NamedType param) { @@ -1391,10 +1405,6 @@ return EncodableValue(EncodableList{ final String errorGetter; const String nullValue = 'EncodableValue()'; - String enumPrefix = ''; - if (returnType.isEnum) { - enumPrefix = '(int) '; - } if (returnType.isVoid) { nonErrorPath = '${prefix}wrapped.push_back($nullValue);'; errorCondition = 'output.has_value()'; @@ -1404,22 +1414,21 @@ return EncodableValue(EncodableList{ getHostDatatype(returnType, _shortBaseCppTypeForBuiltinDartType); const String extractedValue = 'std::move(output).TakeValue()'; - final String wrapperType = hostType.isBuiltin || returnType.isEnum - ? 'EncodableValue' - : 'CustomEncodableValue'; + final String wrapperType = + hostType.isBuiltin ? 'EncodableValue' : 'CustomEncodableValue'; if (returnType.isNullable) { // The value is a std::optional, so needs an extra layer of // handling. nonErrorPath = ''' ${prefix}auto output_optional = $extractedValue; ${prefix}if (output_optional) { -$prefix\twrapped.push_back($wrapperType(${enumPrefix}std::move(output_optional).value())); +$prefix\twrapped.push_back($wrapperType(std::move(output_optional).value())); $prefix} else { $prefix\twrapped.push_back($nullValue); $prefix}'''; } else { nonErrorPath = - '${prefix}wrapped.push_back($wrapperType($enumPrefix$extractedValue));'; + '${prefix}wrapped.push_back($wrapperType($extractedValue));'; } errorCondition = 'output.has_error()'; errorGetter = 'error'; @@ -1452,32 +1461,20 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));'''; /// Returns the expression to create an EncodableValue from a host API argument /// with the given [variableName] and types. - /// - /// If [preSerializeClasses] is true, custom classes will be returned as - /// encodable lists rather than CustomEncodableValues; see - /// https://github.com/flutter/flutter/issues/119351 for why this is currently - /// needed. - String _wrappedHostApiArgumentExpression(Root root, String variableName, - TypeDeclaration dartType, HostDatatype hostType, - {required bool preSerializeClasses}) { + String _wrappedHostApiArgumentExpression( + Root root, + String variableName, + TypeDeclaration dartType, + HostDatatype hostType, + bool isNestedClass, + ) { final String encodableValue; - if (!hostType.isBuiltin && - root.classes.any((Class c) => c.name == dartType.baseName)) { - if (preSerializeClasses) { - final String operator = - hostType.isNullable || _isPointerField(hostType) ? '->' : '.'; - encodableValue = - 'EncodableValue($variableName${operator}ToEncodableList())'; - } else { - final String nonNullValue = - hostType.isNullable ? '*$variableName' : variableName; - encodableValue = 'CustomEncodableValue($nonNullValue)'; - } - } else if (!hostType.isBuiltin && - root.enums.any((Enum e) => e.name == dartType.baseName)) { + if (!hostType.isBuiltin) { final String nonNullValue = - hostType.isNullable ? '(*$variableName)' : variableName; - encodableValue = 'EncodableValue((int)$nonNullValue)'; + hostType.isNullable || (!hostType.isEnum && isNestedClass) + ? '*$variableName' + : variableName; + encodableValue = 'CustomEncodableValue($nonNullValue)'; } else if (dartType.baseName == 'Object') { final String operator = hostType.isNullable ? '*' : ''; encodableValue = '$operator$variableName'; @@ -1523,21 +1520,16 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));'''; indent.writeln( 'const auto* $argName = std::get_if<${hostType.datatype}>(&$encodableArgName);'); } else if (hostType.isEnum) { - final String valueVarName = '${argName}_value'; - indent.writeln( - 'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();'); - if (apiType == ApiType.flutter) { - indent.writeln( - 'const ${hostType.datatype} enum_$argName = (${hostType.datatype})$valueVarName;'); - indent.writeln( - 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &enum_$argName;'); - } else { - indent.writeln( - 'const auto $argName = $encodableArgName.IsNull() ? std::nullopt : std::make_optional<${hostType.datatype}>(static_cast<${hostType.datatype}>(${argName}_value));'); - } + indent.format(''' +${hostType.datatype} ${argName}_value; +const ${hostType.datatype}* $argName = nullptr; +if (!$encodableArgName.IsNull()) { + ${argName}_value = ${_classReferenceFromEncodableValue(hostType, encodableArgName)}; + $argName = &${argName}_value; +}'''); } else { indent.writeln( - 'const auto* $argName = &(std::any_cast(std::get($encodableArgName)));'); + 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &(${_classReferenceFromEncodableValue(hostType, encodableArgName)});'); } } else { // Non-nullable arguments are either passed by value or reference, but the @@ -1557,12 +1549,9 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));'''; } else if (hostType.isBuiltin) { indent.writeln( 'const auto& $argName = std::get<${hostType.datatype}>($encodableArgName);'); - } else if (hostType.isEnum) { - indent.writeln( - 'const ${hostType.datatype}& $argName = (${hostType.datatype})$encodableArgName.LongValue();'); } else { indent.writeln( - 'const auto& $argName = std::any_cast(std::get($encodableArgName));'); + 'const auto& $argName = ${_classReferenceFromEncodableValue(hostType, encodableArgName)};'); } } } @@ -1573,6 +1562,13 @@ ${prefix}reply(EncodableValue(std::move(wrapped)));'''; String? _shortBaseCppTypeForBuiltinDartType(TypeDeclaration type) { return _baseCppTypeForBuiltinDartType(type, includeFlutterNamespace: false); } + + /// Returns the code to extract a `const {type.datatype}&` from an EncodableValue + /// variable [variableName] that contains an instance of [type]. + String _classReferenceFromEncodableValue( + HostDatatype type, String variableName) { + return 'std::any_cast(std::get($variableName))'; + } } /// Contains information about a host function argument. @@ -1594,8 +1590,6 @@ class _IndexedField { final NamedType field; } -String _getCodecSerializerName(Api api) => '${api.name}CodecSerializer'; - const String _encodablePrefix = 'encodable'; String _getArgumentName(int count, NamedType argument) => diff --git a/packages/pigeon/lib/dart/templates.dart b/packages/pigeon/lib/dart/templates.dart index ce986b888db..48c5859dc3d 100644 --- a/packages/pigeon/lib/dart/templates.dart +++ b/packages/pigeon/lib/dart/templates.dart @@ -215,113 +215,6 @@ class $instanceManagerClassName { '''; } -/// Creates the `InstanceManagerApi` with the passed string values. -String instanceManagerApiTemplate({ - required String dartPackageName, - required String pigeonChannelCodecVarName, -}) { - const String apiName = '${instanceManagerClassName}Api'; - - final String removeStrongReferenceName = makeChannelNameWithStrings( - apiName: apiName, - methodName: 'removeStrongReference', - dartPackageName: dartPackageName, - ); - - final String clearName = makeChannelNameWithStrings( - apiName: apiName, - methodName: 'clear', - dartPackageName: dartPackageName, - ); - - return ''' -/// Generated API for managing the Dart and native `$instanceManagerClassName`s. -class _$apiName { - /// Constructor for [_$apiName]. - _$apiName({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; - - final BinaryMessenger? _binaryMessenger; - - static const MessageCodec $pigeonChannelCodecVarName = - StandardMessageCodec(); - - static void setUpMessageHandlers({ - BinaryMessenger? binaryMessenger, - $instanceManagerClassName? instanceManager, - }) { - const String channelName = - r'$removeStrongReferenceName'; - final BasicMessageChannel channel = BasicMessageChannel( - channelName, - $pigeonChannelCodecVarName, - binaryMessenger: binaryMessenger, - ); - channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for \$channelName was null.', - ); - final int? identifier = message as int?; - assert( - identifier != null, - r'Argument for \$channelName, expected non-null int.', - ); - (instanceManager ?? $instanceManagerClassName.instance).remove(identifier!); - return; - }); - } - - Future removeStrongReference(int identifier) async { - const String channelName = - r'$removeStrongReferenceName'; - final BasicMessageChannel channel = BasicMessageChannel( - channelName, - $pigeonChannelCodecVarName, - binaryMessenger: _binaryMessenger, - ); - final List? replyList = - await channel.send(identifier) as List?; - if (replyList == null) { - throw _createConnectionError(channelName); - } else if (replyList.length > 1) { - throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], - ); - } else { - return; - } - } - - /// Clear the native `$instanceManagerClassName`. - /// - /// This is typically called after a hot restart. - Future clear() async { - const String channelName = - r'$clearName'; - final BasicMessageChannel channel = BasicMessageChannel( - channelName, - $pigeonChannelCodecVarName, - binaryMessenger: _binaryMessenger, - ); - final List? replyList = await channel.send(null) as List?; - if (replyList == null) { - throw _createConnectionError(channelName); - } else if (replyList.length > 1) { - throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], - ); - } else { - return; - } - } -}'''; -} - /// The base class for all ProxyApis. /// /// All Dart classes generated as a ProxyApi extends this one. @@ -369,7 +262,7 @@ abstract class $proxyApiBaseClassName { /// adds support to convert instances to their corresponding identifier from an /// `InstanceManager` and vice versa. const String proxyApiBaseCodec = ''' -class $_proxyApiCodecName extends StandardMessageCodec { +class $_proxyApiCodecName extends _PigeonCodec { const $_proxyApiCodecName(this.instanceManager); final $instanceManagerClassName instanceManager; @override diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index adc0aef5cc9..b5c0e42d365 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -15,14 +15,8 @@ import 'generator_tools.dart'; /// Documentation comment open symbol. const String _docCommentPrefix = '///'; -/// Prefix for all local variables in host API methods. -/// -/// This lowers the chances of variable name collisions with -/// user defined parameters. -const String _varNamePrefix = '__pigeon_'; - /// Name of the variable that contains the message channel suffix for APIs. -const String _suffixVarName = '${_varNamePrefix}messageChannelSuffix'; +const String _suffixVarName = '${varNamePrefix}messageChannelSuffix'; /// Name of the `InstanceManager` variable for a ProxyApi class; const String _instanceManagerVarName = @@ -35,8 +29,8 @@ const String _pigeonChannelCodec = 'pigeonChannelCodec'; const DocumentCommentSpecification _docCommentSpec = DocumentCommentSpecification(_docCommentPrefix); -/// The standard codec for Flutter, used for any non custom codecs and extended for custom codecs. -const String _standardMessageCodec = 'StandardMessageCodec'; +/// The custom codec used for all pigeon APIs. +const String _pigeonCodec = '_PigeonCodec'; /// Options that control how Dart code will be generated. class DartOptions { @@ -229,18 +223,7 @@ class DartGenerator extends StructuredGenerator { indent.addScoped('[', '];', () { for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) { - final String conditional = field.type.isNullable ? '?' : ''; - if (field.type.isClass) { - indent.writeln( - '${field.name}$conditional.encode(),', - ); - } else if (field.type.isEnum) { - indent.writeln( - '${field.name}$conditional.index,', - ); - } else { - indent.writeln('${field.name},'); - } + indent.writeln('${field.name},'); } }); }); @@ -260,29 +243,7 @@ class DartGenerator extends StructuredGenerator { final String genericType = _makeGenericTypeArguments(field.type); final String castCall = _makeGenericCastCall(field.type); final String nullableTag = field.type.isNullable ? '?' : ''; - if (field.type.isClass) { - final String nonNullValue = - '${field.type.baseName}.decode($resultAt! as List)'; - if (field.type.isNullable) { - indent.format(''' -$resultAt != null -\t\t? $nonNullValue -\t\t: null''', leadingSpace: false, trailingNewline: false); - } else { - indent.add(nonNullValue); - } - } else if (field.type.isEnum) { - final String nonNullValue = - '${field.type.baseName}.values[$resultAt! as int]'; - if (field.type.isNullable) { - indent.format(''' -$resultAt != null -\t\t? $nonNullValue -\t\t: null''', leadingSpace: false, trailingNewline: false); - } else { - indent.add(nonNullValue); - } - } else if (field.type.typeArguments.isNotEmpty) { + if (field.type.typeArguments.isNotEmpty) { indent.add( '($resultAt as $genericType?)$castCallPrefix$castCall', ); @@ -315,6 +276,69 @@ $resultAt != null }); } + @override + void writeGeneralCodec( + DartOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + indent.newln(); + final Iterable enumeratedTypes = getEnumeratedTypes(root); + indent.newln(); + indent.write('class $_pigeonCodec extends StandardMessageCodec'); + indent.addScoped(' {', '}', () { + indent.writeln('const $_pigeonCodec();'); + if (enumeratedTypes.isNotEmpty) { + indent.writeln('@override'); + indent.write('void writeValue(WriteBuffer buffer, Object? value) '); + indent.addScoped('{', '}', () { + enumerate(enumeratedTypes, + (int index, final EnumeratedType customType) { + indent.writeScoped('if (value is ${customType.name}) {', '} else ', + () { + indent.writeln('buffer.putUint8(${customType.enumeration});'); + if (customType.type == CustomTypes.customClass) { + indent.writeln('writeValue(buffer, value.encode());'); + } else if (customType.type == CustomTypes.customEnum) { + indent.writeln('writeValue(buffer, value.index);'); + } + }, addTrailingNewline: false); + }); + indent.addScoped('{', '}', () { + indent.writeln('super.writeValue(buffer, value);'); + }); + }); + indent.newln(); + indent.writeln('@override'); + indent.write('Object? readValueOfType(int type, ReadBuffer buffer) '); + indent.addScoped('{', '}', () { + indent.write('switch (type) '); + indent.addScoped('{', '}', () { + for (final EnumeratedType customType in enumeratedTypes) { + indent.writeln('case ${customType.enumeration}: '); + indent.nest(1, () { + if (customType.type == CustomTypes.customClass) { + indent.writeln( + 'return ${customType.name}.decode(readValue(buffer)!);'); + } else if (customType.type == CustomTypes.customEnum) { + indent + .writeln('final int? value = readValue(buffer) as int?;'); + indent.writeln( + 'return value == null ? null : ${customType.name}.values[value];'); + } + }); + } + indent.writeln('default:'); + indent.nest(1, () { + indent.writeln('return super.readValueOfType(type, buffer);'); + }); + }); + }); + } + }); + } + /// Writes the code for host [Api], [api]. /// Example: /// class FooCodec extends StandardMessageCodec {...} @@ -334,11 +358,6 @@ $resultAt != null bool isMockHandler = false, required String dartPackageName, }) { - String codecName = _standardMessageCodec; - if (getCodecClasses(api, root).isNotEmpty) { - codecName = _getCodecName(api); - _writeCodec(indent, codecName, api, root); - } indent.newln(); addDocumentationComments( indent, api.documentationComments, _docCommentSpec); @@ -350,7 +369,7 @@ $resultAt != null 'static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance;'); } indent.writeln( - 'static const MessageCodec $_pigeonChannelCodec = $codecName();'); + 'static const MessageCodec $_pigeonChannelCodec = $_pigeonCodec();'); indent.newln(); for (final Method func in api.methods) { addDocumentationComments( @@ -377,6 +396,7 @@ $resultAt != null name: func.name, parameters: func.parameters, returnType: func.returnType, + addSuffixVariable: true, channelName: channelNameFunc == null ? makeChannelName(api, func, dartPackageName) : channelNameFunc(func), @@ -413,11 +433,6 @@ $resultAt != null AstHostApi api, { required String dartPackageName, }) { - String codecName = _standardMessageCodec; - if (getCodecClasses(api, root).isNotEmpty) { - codecName = _getCodecName(api); - _writeCodec(indent, codecName, api, root); - } indent.newln(); bool first = true; addDocumentationComments( @@ -429,13 +444,13 @@ $resultAt != null /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. ${api.name}({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) - : ${_varNamePrefix}binaryMessenger = binaryMessenger, - ${_varNamePrefix}messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.\$messageChannelSuffix' : ''; -final BinaryMessenger? ${_varNamePrefix}binaryMessenger; + : ${varNamePrefix}binaryMessenger = binaryMessenger, + ${varNamePrefix}messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.\$messageChannelSuffix' : ''; +final BinaryMessenger? ${varNamePrefix}binaryMessenger; '''); indent.writeln( - 'static const MessageCodec $_pigeonChannelCodec = $codecName();'); + 'static const MessageCodec $_pigeonChannelCodec = $_pigeonCodec();'); indent.newln(); indent.writeln('final String $_suffixVarName;'); indent.newln(); @@ -483,11 +498,204 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; Indent indent, { required String dartPackageName, }) { + const String apiName = '${instanceManagerClassName}Api'; + + final cb.Parameter binaryMessengerParameter = cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = 'binaryMessenger' + ..type = cb.refer('BinaryMessenger?') + ..named = true, + ); + + final cb.Field binaryMessengerField = cb.Field( + (cb.FieldBuilder builder) => builder + ..name = '${varNamePrefix}binaryMessenger' + ..type = cb.refer('BinaryMessenger?') + ..modifier = cb.FieldModifier.final$, + ); + + final String removeStrongReferenceName = makeChannelNameWithStrings( + apiName: apiName, + methodName: 'removeStrongReference', + dartPackageName: dartPackageName, + ); + + final cb.Class instanceManagerApi = cb.Class( + (cb.ClassBuilder builder) => builder + ..name = '_$apiName' + ..docs.add( + '/// Generated API for managing the Dart and native `$instanceManagerClassName`s.', + ) + ..constructors.add( + cb.Constructor( + (cb.ConstructorBuilder builder) { + builder + ..docs.add('/// Constructor for [_$apiName].') + ..optionalParameters.add(binaryMessengerParameter) + ..initializers.add( + cb.Code( + '${binaryMessengerField.name} = ${binaryMessengerParameter.name}', + ), + ); + }, + ), + ) + ..fields.addAll( + [ + binaryMessengerField, + cb.Field( + (cb.FieldBuilder builder) { + builder + ..name = _pigeonChannelCodec + ..type = cb.refer('MessageCodec') + ..static = true + ..modifier = cb.FieldModifier.constant + ..assignment = const cb.Code('StandardMessageCodec()'); + }, + ) + ], + ) + ..methods.add( + cb.Method( + (cb.MethodBuilder builder) { + builder + ..name = 'setUpMessageHandlers' + ..static = true + ..returns = cb.refer('void') + ..optionalParameters.addAll([ + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = '${classMemberNamePrefix}clearHandlers' + ..type = cb.refer('bool') + ..named = true + ..defaultTo = const cb.Code('false'), + ), + binaryMessengerParameter, + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = 'instanceManager' + ..named = true + ..type = cb.refer('$instanceManagerClassName?'), + ), + ]) + ..body = cb.Block.of( + cb.Block( + (cb.BlockBuilder builder) { + final StringBuffer messageHandlerSink = StringBuffer(); + _writeFlutterMethodMessageHandler( + Indent(messageHandlerSink), + name: 'removeStrongReferenceName', + parameters: [ + Parameter( + name: 'identifier', + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + ) + ], + returnType: const TypeDeclaration.voidDeclaration(), + channelName: removeStrongReferenceName, + isMockHandler: false, + isAsynchronous: false, + nullHandlerExpression: + '${classMemberNamePrefix}clearHandlers', + onCreateApiCall: ( + String methodName, + Iterable parameters, + Iterable safeArgumentNames, + ) { + return '(instanceManager ?? $instanceManagerClassName.instance).remove(${safeArgumentNames.single})'; + }, + ); + builder.statements.add( + cb.Code(messageHandlerSink.toString()), + ); + }, + ).statements, + ); + }, + ), + ) + ..methods.addAll( + [ + cb.Method( + (cb.MethodBuilder builder) { + builder + ..name = 'removeStrongReference' + ..returns = cb.refer('Future') + ..modifier = cb.MethodModifier.async + ..requiredParameters.add( + cb.Parameter( + (cb.ParameterBuilder builder) => builder + ..name = 'identifier' + ..type = cb.refer('int'), + ), + ) + ..body = cb.Block( + (cb.BlockBuilder builder) { + final StringBuffer messageCallSink = StringBuffer(); + _writeHostMethodMessageCall( + Indent(messageCallSink), + addSuffixVariable: false, + channelName: removeStrongReferenceName, + parameters: [ + Parameter( + name: 'identifier', + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + ), + ], + returnType: const TypeDeclaration.voidDeclaration(), + ); + builder.statements.addAll([ + cb.Code(messageCallSink.toString()), + ]); + }, + ); + }, + ), + cb.Method( + (cb.MethodBuilder builder) { + builder + ..name = 'clear' + ..returns = cb.refer('Future') + ..modifier = cb.MethodModifier.async + ..docs.addAll([ + '/// Clear the native `$instanceManagerClassName`.', + '///', + '/// This is typically called after a hot restart.', + ]) + ..body = cb.Block( + (cb.BlockBuilder builder) { + final StringBuffer messageCallSink = StringBuffer(); + _writeHostMethodMessageCall( + Indent(messageCallSink), + addSuffixVariable: false, + channelName: makeChannelNameWithStrings( + apiName: apiName, + methodName: 'clear', + dartPackageName: dartPackageName, + ), + parameters: [], + returnType: const TypeDeclaration.voidDeclaration(), + ); + builder.statements.addAll([ + cb.Code(messageCallSink.toString()), + ]); + }, + ); + }, + ), + ], + ), + ); + + final cb.DartEmitter emitter = cb.DartEmitter(useNullSafetySyntax: true); indent.format( - instanceManagerApiTemplate( - dartPackageName: dartPackageName, - pigeonChannelCodecVarName: _pigeonChannelCodec, - ), + DartFormatter().format('${instanceManagerApi.accept(emitter)}'), ); } @@ -512,7 +720,7 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; // Each API has a private codec instance used by every host method, // constructor, or non-static field. - final String codecInstanceName = '${_varNamePrefix}codec${api.name}'; + final String codecInstanceName = '${varNamePrefix}codec${api.name}'; // AST class used by code_builder to generate the code. final cb.Class proxyApi = cb.Class( @@ -643,6 +851,8 @@ final BinaryMessenger? ${_varNamePrefix}binaryMessenger; relativeDartPath.replaceFirst(RegExp(r'^.*/lib/'), ''); indent.writeln("import 'package:$dartOutputPackageName/$path';"); } + writeGeneralCodec(generatorOptions, root, indent, + dartPackageName: dartPackageName); for (final AstHostApi api in root.apis.whereType()) { if (api.dartHostTestHandler != null) { final AstFlutterApi mockApi = AstFlutterApi( @@ -782,28 +992,24 @@ PlatformException _createConnectionError(String channelName) { final Iterable argExpressions = indexMap(parameters, (int index, NamedType type) { final String name = _getParameterName(index, type); - if (type.type.isEnum) { - return '$name${type.type.isNullable ? '?' : ''}.index'; - } else { - return name; - } + return name; }); sendArgument = '[${argExpressions.join(', ')}]'; } final String channelSuffix = addSuffixVariable ? '\$$_suffixVarName' : ''; final String constOrFinal = addSuffixVariable ? 'final' : 'const'; indent.writeln( - "$constOrFinal String ${_varNamePrefix}channelName = '$channelName$channelSuffix';"); + "$constOrFinal String ${varNamePrefix}channelName = '$channelName$channelSuffix';"); indent.writeScoped( - 'final BasicMessageChannel ${_varNamePrefix}channel = BasicMessageChannel(', + 'final BasicMessageChannel ${varNamePrefix}channel = BasicMessageChannel(', ');', () { - indent.writeln('${_varNamePrefix}channelName,'); + indent.writeln('${varNamePrefix}channelName,'); indent.writeln('$_pigeonChannelCodec,'); - indent.writeln('binaryMessenger: ${_varNamePrefix}binaryMessenger,'); + indent.writeln('binaryMessenger: ${varNamePrefix}binaryMessenger,'); }); final String returnTypeName = _makeGenericTypeArguments(returnType); final String genericCastCall = _makeGenericCastCall(returnType); - const String accessor = '${_varNamePrefix}replyList[0]'; + const String accessor = '${varNamePrefix}replyList[0]'; // Avoid warnings from pointlessly casting to `Object?`. final String nullablyTypedAccessor = returnTypeName == 'Object' ? accessor @@ -811,37 +1017,29 @@ PlatformException _createConnectionError(String channelName) { final String nullHandler = returnType.isNullable ? (genericCastCall.isEmpty ? '' : '?') : '!'; String returnStatement = 'return'; - if (returnType.isEnum) { - if (returnType.isNullable) { - returnStatement = - '$returnStatement ($accessor as int?) == null ? null : $returnTypeName.values[$accessor! as int]'; - } else { - returnStatement = - '$returnStatement $returnTypeName.values[$accessor! as int]'; - } - } else if (!returnType.isVoid) { + if (!returnType.isVoid) { returnStatement = '$returnStatement $nullablyTypedAccessor$nullHandler$genericCastCall'; } returnStatement = '$returnStatement;'; indent.format(''' -final List? ${_varNamePrefix}replyList = -\t\tawait ${_varNamePrefix}channel.send($sendArgument) as List?; -if (${_varNamePrefix}replyList == null) { -\tthrow _createConnectionError(${_varNamePrefix}channelName); -} else if (${_varNamePrefix}replyList.length > 1) { +final List? ${varNamePrefix}replyList = +\t\tawait ${varNamePrefix}channel.send($sendArgument) as List?; +if (${varNamePrefix}replyList == null) { +\tthrow _createConnectionError(${varNamePrefix}channelName); +} else if (${varNamePrefix}replyList.length > 1) { \tthrow PlatformException( -\t\tcode: ${_varNamePrefix}replyList[0]! as String, -\t\tmessage: ${_varNamePrefix}replyList[1] as String?, -\t\tdetails: ${_varNamePrefix}replyList[2], +\t\tcode: ${varNamePrefix}replyList[0]! as String, +\t\tmessage: ${varNamePrefix}replyList[1] as String?, +\t\tdetails: ${varNamePrefix}replyList[2], \t);'''); // On iOS we can return nil from functions to accommodate error // handling. Returning a nil value and not returning an error is an // exception. if (!returnType.isNullable && !returnType.isVoid) { indent.format(''' -} else if (${_varNamePrefix}replyList[0] == null) { +} else if (${varNamePrefix}replyList[0] == null) { \tthrow PlatformException( \t\tcode: 'null-error', \t\tmessage: 'Host platform returned null value for non-null return value.', @@ -870,19 +1068,19 @@ if (${_varNamePrefix}replyList == null) { indent.write(''); indent.addScoped('{', '}', () { indent.writeln( - 'final BasicMessageChannel ${_varNamePrefix}channel = BasicMessageChannel(', + 'final BasicMessageChannel ${varNamePrefix}channel = BasicMessageChannel(', ); indent.nest(2, () { final String channelSuffix = - addSuffixVariable ? '' : r'$messageChannelSuffix'; + addSuffixVariable ? r'$messageChannelSuffix' : ''; indent.writeln("'$channelName$channelSuffix', $_pigeonChannelCodec,"); indent.writeln( 'binaryMessenger: binaryMessenger);', ); }); final String messageHandlerSetterWithOpeningParentheses = isMockHandler - ? '_testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(${_varNamePrefix}channel, ' - : '${_varNamePrefix}channel.setMessageHandler('; + ? '_testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(${varNamePrefix}channel, ' + : '${varNamePrefix}channel.setMessageHandler('; indent.write('if ($nullHandlerExpression) '); indent.addScoped('{', '}', () { indent.writeln('${messageHandlerSetterWithOpeningParentheses}null);'); @@ -915,13 +1113,10 @@ if (${_varNamePrefix}replyList == null) { final String castCall = _makeGenericCastCall(arg.type); final String leftHandSide = 'final $argType? $argName'; - if (arg.type.isEnum) { - indent.writeln( - '$leftHandSide = $argsArray[$count] == null ? null : $argType.values[$argsArray[$count]! as int];'); - } else { - indent.writeln( - '$leftHandSide = ($argsArray[$count] as $genericArgType?)${castCall.isEmpty ? '' : '?$castCall'};'); - } + + indent.writeln( + '$leftHandSide = ($argsArray[$count] as $genericArgType?)${castCall.isEmpty ? '' : '?$castCall'};'); + if (!arg.type.isNullable) { indent.writeln('assert($argName != null,'); indent.writeln( @@ -951,12 +1146,9 @@ if (${_varNamePrefix}replyList == null) { } const String returnExpression = 'output'; - final String nullability = returnType.isNullable ? '?' : ''; - final String valueExtraction = - returnType.isEnum ? '$nullability.index' : ''; final String returnStatement = isMockHandler - ? 'return [$returnExpression$valueExtraction];' - : 'return wrapResponse(result: $returnExpression$valueExtraction);'; + ? 'return [$returnExpression];' + : 'return wrapResponse(result: $returnExpression);'; indent.writeln(returnStatement); } }, addTrailingNewline: false); @@ -1079,7 +1271,7 @@ if (${_varNamePrefix}replyList == null) { channelName: channelName, parameters: [ Parameter( - name: '${_varNamePrefix}instanceIdentifier', + name: '${varNamePrefix}instanceIdentifier', type: const TypeDeclaration( baseName: 'int', isNullable: false, @@ -1098,12 +1290,12 @@ if (${_varNamePrefix}replyList == null) { builder.statements.addAll([ const cb.Code( - 'final int ${_varNamePrefix}instanceIdentifier = $_instanceManagerVarName.addDartCreatedInstance(this);', + 'final int ${varNamePrefix}instanceIdentifier = $_instanceManagerVarName.addDartCreatedInstance(this);', ), cb.Code('final $codecName $_pigeonChannelCodec =\n' ' $codecInstanceName;'), cb.Code( - 'final BinaryMessenger? ${_varNamePrefix}binaryMessenger = ${binaryMessengerParameter.name};', + 'final BinaryMessenger? ${varNamePrefix}binaryMessenger = ${binaryMessengerParameter.name};', ), const cb.Code('() async {'), cb.Code(messageCallSink.toString()), @@ -1356,7 +1548,7 @@ if (${_varNamePrefix}replyList == null) { field.documentationComments, _docCommentSpec, )) - ..assignment = cb.Code('$_varNamePrefix${field.name}()'), + ..assignment = cb.Code('$varNamePrefix${field.name}()'), ); } } @@ -1466,7 +1658,6 @@ if (${_varNamePrefix}replyList == null) { _writeFlutterMethodMessageHandler( Indent(messageHandlerSink), name: methodName, - addSuffixVariable: true, parameters: [ Parameter( name: '${classMemberNamePrefix}instanceIdentifier', @@ -1522,7 +1713,6 @@ if (${_varNamePrefix}replyList == null) { _writeFlutterMethodMessageHandler( Indent(messageHandlerSink), name: method.name, - addSuffixVariable: true, parameters: [ Parameter( name: '${classMemberNamePrefix}instance', @@ -1581,11 +1771,11 @@ if (${_varNamePrefix}replyList == null) { yield cb.Method( (cb.MethodBuilder builder) { final String type = _addGenericTypesNullable(field.type); - const String instanceName = '${_varNamePrefix}instance'; + const String instanceName = '${varNamePrefix}instance'; const String identifierInstanceName = - '${_varNamePrefix}instanceIdentifier'; + '${varNamePrefix}instanceIdentifier'; builder - ..name = '$_varNamePrefix${field.name}' + ..name = '$varNamePrefix${field.name}' ..static = field.isStatic ..returns = cb.refer(type) ..body = cb.Block( @@ -1629,7 +1819,7 @@ if (${_varNamePrefix}replyList == null) { cb.Code('final $codecName $_pigeonChannelCodec =\n' ' $codecInstanceName;'), const cb.Code( - 'final BinaryMessenger? ${_varNamePrefix}binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', + 'final BinaryMessenger? ${varNamePrefix}binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', ), const cb.Code( 'final int $identifierInstanceName = $_instanceManagerVarName.addDartCreatedInstance($instanceName);', @@ -1642,7 +1832,7 @@ if (${_varNamePrefix}replyList == null) { 'final $codecName $_pigeonChannelCodec = $codecName($instanceManagerClassName.instance);', ), const cb.Code( - 'final BinaryMessenger ${_varNamePrefix}binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;', + 'final BinaryMessenger ${varNamePrefix}binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;', ), const cb.Code( 'final int $identifierInstanceName = $instanceManagerClassName.instance.addDartCreatedInstance($instanceName);', @@ -1743,7 +1933,7 @@ if (${_varNamePrefix}replyList == null) { 'final $codecName $_pigeonChannelCodec = $codecName($_instanceManagerVarName ?? $instanceManagerClassName.instance);', ), const cb.Code( - 'final BinaryMessenger? ${_varNamePrefix}binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', + 'final BinaryMessenger? ${varNamePrefix}binaryMessenger = ${classMemberNamePrefix}binaryMessenger;', ), cb.Code(messageCallSink.toString()), ]); @@ -1803,60 +1993,6 @@ String _escapeForDartSingleQuotedString(String raw) { .replaceAll(r"'", r"\'"); } -/// Calculates the name of the codec class that will be generated for [api]. -String _getCodecName(Api api) => '_${api.name}Codec'; - -/// Writes the codec that will be used by [api]. -/// Example: -/// -/// class FooCodec extends StandardMessageCodec {...} -void _writeCodec(Indent indent, String codecName, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - indent.newln(); - indent.write('class $codecName extends $_standardMessageCodec'); - indent.addScoped(' {', '}', () { - indent.writeln('const $codecName();'); - indent.writeln('@override'); - indent.write('void writeValue(WriteBuffer buffer, Object? value) '); - indent.addScoped('{', '}', () { - enumerate(codecClasses, (int index, final EnumeratedClass customClass) { - final String ifValue = 'if (value is ${customClass.name}) '; - if (index == 0) { - indent.write(''); - } - indent.add(ifValue); - indent.addScoped('{', '} else ', () { - indent.writeln('buffer.putUint8(${customClass.enumeration});'); - indent.writeln('writeValue(buffer, value.encode());'); - }, addTrailingNewline: false); - }); - indent.addScoped('{', '}', () { - indent.writeln('super.writeValue(buffer, value);'); - }); - }); - indent.newln(); - indent.writeln('@override'); - indent.write('Object? readValueOfType(int type, ReadBuffer buffer) '); - indent.addScoped('{', '}', () { - indent.write('switch (type) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.writeln('case ${customClass.enumeration}: '); - indent.nest(1, () { - indent.writeln( - 'return ${customClass.name}.decode(readValue(buffer)!);'); - }); - } - indent.writeln('default:'); - indent.nest(1, () { - indent.writeln('return super.readValueOfType(type, buffer);'); - }); - }); - }); - }); -} - /// Creates a Dart type where all type arguments are [Objects]. String _makeGenericTypeArguments(TypeDeclaration type) { return type.typeArguments.isNotEmpty diff --git a/packages/pigeon/lib/generator.dart b/packages/pigeon/lib/generator.dart index 3711fd28dbe..952b80e8987 100644 --- a/packages/pigeon/lib/generator.dart +++ b/packages/pigeon/lib/generator.dart @@ -95,6 +95,13 @@ abstract class StructuredGenerator extends Generator { dartPackageName: dartPackageName, ); + writeGeneralCodec( + generatorOptions, + root, + indent, + dartPackageName: dartPackageName, + ); + writeApis( generatorOptions, root, @@ -205,6 +212,14 @@ abstract class StructuredGenerator extends Generator { } } + /// Writes the custom codec to [indent]. + void writeGeneralCodec( + T generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }); + /// Writes a single data class to [indent]. void writeDataClass( T generatorOptions, diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 76c9abf1886..c78761ecef9 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -13,7 +13,13 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '18.0.0'; +const String pigeonVersion = '20.0.2'; + +/// Prefix for all local variables in methods. +/// +/// This lowers the chances of variable name collisions with +/// user defined parameters. +const String varNamePrefix = '__pigeon_'; /// Read all the content from [stdin] to a String. String readStdin() { @@ -31,7 +37,7 @@ bool debugGenerators = false; /// A helper class for managing indentation, wrapping a [StringSink]. class Indent { - /// Constructor which takes a [StringSink] [Ident] will wrap. + /// Constructor which takes a [StringSink] [Indent] will wrap. Indent(this._sink); int _count = 0; @@ -375,16 +381,26 @@ Map mergeMaps( return result; } -/// A class name that is enumerated. -class EnumeratedClass { +/// A type name that is enumerated. +class EnumeratedType { /// Constructor. - EnumeratedClass(this.name, this.enumeration); + EnumeratedType(this.name, this.enumeration, this.type, + {this.associatedClass, this.associatedEnum}); - /// The name of the class. + /// The name of the type. final String name; /// The enumeration of the class. final int enumeration; + + /// The type of custom type of the enumerated type. + final CustomTypes type; + + /// The associated Class that is represented by the [EnumeratedType]. + final Class? associatedClass; + + /// The associated Enum that is represented by the [EnumeratedType]. + final Enum? associatedEnum; } /// Supported basic datatypes. @@ -404,7 +420,7 @@ const List validTypes = [ /// Custom codecs' custom types are enumerated from 255 down to this number to /// avoid collisions with the StandardMessageCodec. -const int _minimumCodecFieldKey = 128; +const int _minimumCodecFieldKey = 129; Iterable _getTypeArguments(TypeDeclaration type) sync* { for (final TypeDeclaration typeArg in type.typeArguments) { @@ -488,39 +504,42 @@ Map> getReferencedTypes( return references.map; } -/// Returns true if the concrete type cannot be determined at compile-time. -bool _isConcreteTypeAmbiguous(TypeDeclaration type) { - return (type.baseName == 'List' && type.typeArguments.isEmpty) || - (type.baseName == 'Map' && type.typeArguments.isEmpty) || - type.baseName == 'Object'; +/// All custom definable data types. +enum CustomTypes { + /// A custom Class. + customClass, + + /// A custom Enum. + customEnum, } -/// Given an [Api], return the enumerated classes that must exist in the codec +/// Return the enumerated types that must exist in the codec /// where the enumeration should be the key used in the buffer. -Iterable getCodecClasses(Api api, Root root) sync* { - final Set enumNames = root.enums.map((Enum e) => e.name).toSet(); - final Map> referencedTypes = - getReferencedTypes([api], root.classes); - final Iterable allTypeNames = - referencedTypes.keys.any(_isConcreteTypeAmbiguous) - ? root.classes.map((Class aClass) => aClass.name) - : referencedTypes.keys.map((TypeDeclaration e) => e.baseName); - final List sortedNames = allTypeNames - .where((String element) => - element != 'void' && - !validTypes.contains(element) && - !enumNames.contains(element)) - .toList(); - sortedNames.sort(); - int enumeration = _minimumCodecFieldKey; +Iterable getEnumeratedTypes(Root root) sync* { const int maxCustomClassesPerApi = 255 - _minimumCodecFieldKey; - if (sortedNames.length > maxCustomClassesPerApi) { + if (root.classes.length + root.enums.length > maxCustomClassesPerApi) { throw Exception( - "Pigeon doesn't support more than $maxCustomClassesPerApi referenced custom classes per API, try splitting up your APIs."); + "Pigeon doesn't currently support more than $maxCustomClassesPerApi referenced custom classes per file."); } - for (final String name in sortedNames) { - yield EnumeratedClass(name, enumeration); - enumeration += 1; + int index = 0; + for (final Class customClass in root.classes) { + yield EnumeratedType( + customClass.name, + index + _minimumCodecFieldKey, + CustomTypes.customClass, + associatedClass: customClass, + ); + index += 1; + } + + for (final Enum customEnum in root.enums) { + yield EnumeratedType( + customEnum.name, + index + _minimumCodecFieldKey, + CustomTypes.customEnum, + associatedEnum: customEnum, + ); + index += 1; } } @@ -679,3 +698,13 @@ class OutputFileOptions { /// Options for specified language across all file types. T languageOptions; } + +/// Converts strings to Upper Camel Case. +String toUpperCamelCase(String text) { + final RegExp separatorPattern = RegExp(r'[ _-]'); + return text.split(separatorPattern).map((String word) { + return word.isEmpty + ? '' + : word.substring(0, 1).toUpperCase() + word.substring(1); + }).join(); +} diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index 5e8fbb2c045..329bf37dab0 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -26,7 +26,7 @@ const DocumentCommentSpecification _docCommentSpec = ); /// The standard codec for Flutter, used for any non custom codecs and extended for custom codecs. -const String _standardMessageCodec = 'StandardMessageCodec'; +const String _codecName = 'PigeonCodec'; /// Options that control how Java code will be generated. class JavaOptions { @@ -141,6 +141,7 @@ class JavaGenerator extends StructuredGenerator { indent.writeln('import java.util.HashMap;'); indent.writeln('import java.util.List;'); indent.writeln('import java.util.Map;'); + indent.writeln('import java.util.Objects;'); indent.newln(); } @@ -233,6 +234,7 @@ class JavaGenerator extends StructuredGenerator { indent.writeln('${classDefinition.name}() {}'); indent.newln(); } + _writeEquality(indent, classDefinition); _writeClassBuilder(generatorOptions, root, indent, classDefinition); writeClassEncode( @@ -282,6 +284,62 @@ class JavaGenerator extends StructuredGenerator { }); } + void _writeEquality(Indent indent, Class classDefinition) { + // Implement equals(...). + indent.writeln('@Override'); + indent.writeScoped('public boolean equals(Object o) {', '}', () { + indent.writeln('if (this == o) { return true; }'); + indent.writeln( + 'if (o == null || getClass() != o.getClass()) { return false; }'); + indent.writeln( + '${classDefinition.name} that = (${classDefinition.name}) o;'); + final Iterable checks = classDefinition.fields.map( + (NamedType field) { + // Objects.equals only does pointer equality for array types. + if (_javaTypeIsArray(field.type)) { + return 'Arrays.equals(${field.name}, that.${field.name})'; + } + return field.type.isNullable + ? 'Objects.equals(${field.name}, that.${field.name})' + : '${field.name}.equals(that.${field.name})'; + }, + ); + indent.writeln('return ${checks.join(' && ')};'); + }); + indent.newln(); + + // Implement hashCode(). + indent.writeln('@Override'); + indent.writeScoped('public int hashCode() {', '}', () { + // As with equalty checks, arrays need special handling. + final Iterable arrayFieldNames = classDefinition.fields + .where((NamedType field) => _javaTypeIsArray(field.type)) + .map((NamedType field) => field.name); + final Iterable nonArrayFieldNames = classDefinition.fields + .where((NamedType field) => !_javaTypeIsArray(field.type)) + .map((NamedType field) => field.name); + final String nonArrayHashValue = nonArrayFieldNames.isNotEmpty + ? 'Objects.hash(${nonArrayFieldNames.join(', ')})' + : '0'; + + if (arrayFieldNames.isEmpty) { + // Return directly if there are no array variables, to avoid redundant + // variable lint warnings. + indent.writeln('return $nonArrayHashValue;'); + } else { + const String resultVar = '${varNamePrefix}result'; + indent.writeln('int $resultVar = $nonArrayHashValue;'); + // Manually mix in the Arrays.hashCode values. + for (final String name in arrayFieldNames) { + indent.writeln( + '$resultVar = 31 * $resultVar + Arrays.hashCode($name);'); + } + indent.writeln('return $resultVar;'); + } + }); + indent.newln(); + } + void _writeClassBuilder( JavaOptions generatorOptions, Root root, @@ -339,16 +397,7 @@ class JavaGenerator extends StructuredGenerator { 'ArrayList toListResult = new ArrayList(${classDefinition.fields.length});'); for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) { - String toWriteValue = ''; - final String fieldName = field.name; - if (field.type.isClass) { - toWriteValue = '($fieldName == null) ? null : $fieldName.toList()'; - } else if (field.type.isEnum) { - toWriteValue = '$fieldName == null ? null : $fieldName.index'; - } else { - toWriteValue = field.name; - } - indent.writeln('toListResult.add($toWriteValue);'); + indent.writeln('toListResult.add(${field.name});'); } indent.writeln('return toListResult;'); }); @@ -364,7 +413,7 @@ class JavaGenerator extends StructuredGenerator { }) { indent.newln(); indent.write( - 'static @NonNull ${classDefinition.name} fromList(@NonNull ArrayList list) '); + 'static @NonNull ${classDefinition.name} fromList(@NonNull ArrayList ${varNamePrefix}list) '); indent.addScoped('{', '}', () { const String result = 'pigeonResult'; indent.writeln( @@ -373,19 +422,88 @@ class JavaGenerator extends StructuredGenerator { (int index, final NamedType field) { final String fieldVariable = field.name; final String setter = _makeSetter(field); - indent.writeln('Object $fieldVariable = list.get($index);'); - if (field.type.isEnum) { - indent.writeln( - '$result.$setter(${_intToEnum(fieldVariable, field.type.baseName, field.type.isNullable)});'); - } else { - indent.writeln( - '$result.$setter(${_castObject(field, fieldVariable)});'); - } + indent.writeln( + 'Object $fieldVariable = ${varNamePrefix}list.get($index);'); + indent + .writeln('$result.$setter(${_castObject(field, fieldVariable)});'); }); indent.writeln('return $result;'); }); } + @override + void writeGeneralCodec( + JavaOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + final Iterable enumeratedTypes = getEnumeratedTypes(root); + indent.newln(); + indent.write( + 'private static class $_codecName extends StandardMessageCodec '); + indent.addScoped('{', '}', () { + indent.writeln( + 'public static final $_codecName INSTANCE = new $_codecName();'); + indent.newln(); + indent.writeln('private $_codecName() {}'); + indent.newln(); + indent.writeln('@Override'); + indent.write( + 'protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) '); + indent.addScoped('{', '}', () { + indent.write('switch (type) '); + indent.addScoped('{', '}', () { + for (final EnumeratedType customType in enumeratedTypes) { + indent.writeln('case (byte) ${customType.enumeration}:'); + indent.nest(1, () { + if (customType.type == CustomTypes.customClass) { + indent.writeln( + 'return ${customType.name}.fromList((ArrayList) readValue(buffer));'); + } else if (customType.type == CustomTypes.customEnum) { + indent.writeln('Object value = readValue(buffer);'); + indent.writeln( + 'return ${_intToEnum('value', customType.name, true)};'); + } + }); + } + indent.writeln('default:'); + indent.nest(1, () { + indent.writeln('return super.readValueOfType(type, buffer);'); + }); + }); + }); + indent.newln(); + indent.writeln('@Override'); + indent.write( + 'protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) '); + indent.addScoped('{', '}', () { + bool firstClass = true; + for (final EnumeratedType customType in enumeratedTypes) { + if (firstClass) { + indent.write(''); + firstClass = false; + } + indent.add('if (value instanceof ${customType.name}) '); + indent.addScoped('{', '} else ', () { + indent.writeln('stream.write(${customType.enumeration});'); + if (customType.type == CustomTypes.customClass) { + indent.writeln( + 'writeValue(stream, ((${customType.name}) value).toList());'); + } else { + indent.writeln( + 'writeValue(stream, value == null ? null : ((${customType.name}) value).index);'); + } + }, addTrailingNewline: false); + } + indent.addScoped('{', '}', () { + indent.writeln('super.writeValue(stream, value);'); + }); + }); + }); + indent.newln(); + } + /// Writes the code for a flutter [Api], [api]. /// Example: /// public static final class Foo { @@ -403,20 +521,11 @@ class JavaGenerator extends StructuredGenerator { AstFlutterApi api, { required String dartPackageName, }) { - /// Returns an argument name that can be used in a context where it is possible to collide - /// and append `.index` to enums. - String getEnumSafeArgumentExpression(int count, NamedType argument) { - if (argument.type.isEnum) { - return argument.type.isNullable - ? '${_getArgumentName(count, argument)}Arg == null ? null : ${_getArgumentName(count, argument)}Arg.index' - : '${_getArgumentName(count, argument)}Arg.index'; - } + /// Returns an argument name that can be used in a context where it is possible to collide. + String getSafeArgumentExpression(int count, NamedType argument) { return '${_getArgumentName(count, argument)}Arg'; } - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, api, root); - } const List generatedMessages = [ ' Generated class from Pigeon that represents Flutter messages that can be called from Java.' ]; @@ -442,16 +551,10 @@ class JavaGenerator extends StructuredGenerator { }); indent.newln(); indent.writeln('/** Public interface for sending reply. */ '); - final String codecName = _getCodecName(api); indent.writeln('/** The codec used by ${api.name}. */'); indent.write('static @NonNull MessageCodec getCodec() '); indent.addScoped('{', '}', () { - indent.write('return '); - if (getCodecClasses(api, root).isNotEmpty) { - indent.addln('$codecName.INSTANCE;'); - } else { - indent.addln('new $_standardMessageCodec();'); - } + indent.writeln('return $_codecName.INSTANCE;'); }); for (final Method func in api.methods) { @@ -472,7 +575,7 @@ class JavaGenerator extends StructuredGenerator { final Iterable argNames = indexMap(func.parameters, _getSafeArgumentName); final Iterable enumSafeArgNames = - indexMap(func.parameters, getEnumSafeArgumentExpression); + indexMap(func.parameters, getSafeArgumentExpression); if (func.parameters.length == 1) { sendArgument = 'new ArrayList(Collections.singletonList(${enumSafeArgNames.first}))'; @@ -527,14 +630,6 @@ class JavaGenerator extends StructuredGenerator { if (func.returnType.baseName == 'int') { outputExpression = 'listReply.get(0) == null ? null : ((Number) listReply.get(0)).longValue();'; - } else if (func.returnType.isEnum) { - if (func.returnType.isNullable) { - outputExpression = - 'listReply.get(0) == null ? null : $returnType.values()[(int) listReply.get(0)];'; - } else { - outputExpression = - '$returnType.values()[(int) listReply.get(0)];'; - } } else { outputExpression = '${_cast('listReply.get(0)', javaType: returnType)};'; @@ -587,9 +682,6 @@ class JavaGenerator extends StructuredGenerator { AstHostApi api, { required String dartPackageName, }) { - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, api, root); - } const List generatedMessages = [ ' Generated interface from Pigeon that represents a handler of messages from Flutter.' ]; @@ -602,16 +694,10 @@ class JavaGenerator extends StructuredGenerator { _writeInterfaceMethod(generatorOptions, root, indent, api, method); } indent.newln(); - final String codecName = _getCodecName(api); indent.writeln('/** The codec used by ${api.name}. */'); indent.write('static @NonNull MessageCodec getCodec() '); indent.addScoped('{', '}', () { - indent.write('return '); - if (getCodecClasses(api, root).isNotEmpty) { - indent.addln('$codecName.INSTANCE;'); - } else { - indent.addln('new $_standardMessageCodec();'); - } + indent.writeln('return $_codecName.INSTANCE;'); }); indent.writeln( @@ -717,7 +803,6 @@ class JavaGenerator extends StructuredGenerator { indent.nest(2, () { indent.write('(message, reply) -> '); indent.addScoped('{', '});', () { - String enumTag = ''; final String returnType = method.returnType.isVoid ? 'Void' : _javaTypeForDartType(method.returnType); @@ -739,10 +824,7 @@ class JavaGenerator extends StructuredGenerator { ? '($argName == null) ? null : $argName.longValue()' : argName; String accessor = 'args.get($index)'; - if (arg.type.isEnum) { - accessor = _intToEnum( - accessor, arg.type.baseName, arg.type.isNullable); - } else if (argType != 'Object') { + if (argType != 'Object') { accessor = _cast(accessor, javaType: argType); } indent.writeln('$argType $argName = $accessor;'); @@ -752,16 +834,11 @@ class JavaGenerator extends StructuredGenerator { if (method.isAsynchronous) { final String resultValue = method.returnType.isVoid ? 'null' : 'result'; - if (method.returnType.isEnum) { - enumTag = method.returnType.isNullable - ? ' == null ? null : $resultValue.index' - : '.index'; - } final String resultType = _getResultType(method.returnType); final String resultParam = method.returnType.isVoid ? '' : '$returnType result'; final String addResultArg = - method.returnType.isVoid ? 'null' : '$resultValue$enumTag'; + method.returnType.isVoid ? 'null' : resultValue; const String resultName = 'resultCallback'; indent.format(''' $resultType $resultName = @@ -790,13 +867,8 @@ $resultType $resultName = indent.writeln('$call;'); indent.writeln('wrapped.add(0, null);'); } else { - if (method.returnType.isEnum) { - enumTag = method.returnType.isNullable - ? ' == null ? null : output.index' - : '.index'; - } indent.writeln('$returnType output = $call;'); - indent.writeln('wrapped.add(0, output$enumTag);'); + indent.writeln('wrapped.add(0, output);'); } }); indent.add(' catch (Throwable exception) '); @@ -820,67 +892,6 @@ $resultType $resultName = }); } - /// Writes the codec class that will be used by [api]. - /// Example: - /// private static class FooCodec extends StandardMessageCodec {...} - void _writeCodec(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - final String codecName = _getCodecName(api); - indent.newln(); - indent.write( - 'private static class $codecName extends $_standardMessageCodec '); - indent.addScoped('{', '}', () { - indent.writeln( - 'public static final $codecName INSTANCE = new $codecName();'); - indent.newln(); - indent.writeln('private $codecName() {}'); - indent.newln(); - indent.writeln('@Override'); - indent.write( - 'protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) '); - indent.addScoped('{', '}', () { - indent.write('switch (type) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.writeln('case (byte) ${customClass.enumeration}:'); - indent.nest(1, () { - indent.writeln( - 'return ${customClass.name}.fromList((ArrayList) readValue(buffer));'); - }); - } - indent.writeln('default:'); - indent.nest(1, () { - indent.writeln('return super.readValueOfType(type, buffer);'); - }); - }); - }); - indent.newln(); - indent.writeln('@Override'); - indent.write( - 'protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) '); - indent.addScoped('{', '}', () { - bool firstClass = true; - for (final EnumeratedClass customClass in codecClasses) { - if (firstClass) { - indent.write(''); - firstClass = false; - } - indent.add('if (value instanceof ${customClass.name}) '); - indent.addScoped('{', '} else ', () { - indent.writeln('stream.write(${customClass.enumeration});'); - indent.writeln( - 'writeValue(stream, ((${customClass.name}) value).toList());'); - }, addTrailingNewline: false); - } - indent.addScoped('{', '}', () { - indent.writeln('super.writeValue(stream, value);'); - }); - }); - }); - indent.newln(); - } - void _writeResultInterfaces(Indent indent) { indent.writeln( '/** Asynchronous error handling return type for non-nullable API method returns. */'); @@ -1027,9 +1038,6 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { } } -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; - /// Converts an expression that evaluates to an nullable int to an expression /// that evaluates to a nullable enum. String _intToEnum(String expression, String enumName, bool nullable) => nullable @@ -1072,6 +1080,10 @@ String _javaTypeForBuiltinGenericDartType( } } +bool _javaTypeIsArray(TypeDeclaration type) { + return _javaTypeForBuiltinDartType(type)?.endsWith('[]') ?? false; +} + String? _javaTypeForBuiltinDartType(TypeDeclaration type) { const Map javaTypeForDartTypeMap = { 'bool': 'Boolean', @@ -1123,8 +1135,6 @@ String _castObject(NamedType field, String varName) { field, (TypeDeclaration x) => _javaTypeForBuiltinDartType(x)); if (field.type.baseName == 'int') { return '($varName == null) ? null : (($varName instanceof Integer) ? (Integer) $varName : (${hostDatatype.datatype}) $varName)'; - } else if (field.type.isClass) { - return '($varName == null) ? null : ${hostDatatype.datatype}.fromList((ArrayList) $varName)'; } else { return _cast(varName, javaType: hostDatatype.datatype); } diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index c81df1295ed..1d827aff164 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -25,6 +25,8 @@ const DocumentCommentSpecification _docCommentSpec = blockContinuationToken: _docCommentContinuation, ); +String _codecName = 'PigeonCodec'; + /// Options that control how Kotlin code will be generated. class KotlinOptions { /// Creates a [KotlinOptions] object @@ -33,6 +35,7 @@ class KotlinOptions { this.copyrightHeader, this.errorClassName, this.includeErrorClass = true, + this.fileSpecificClassNameComponent, }); /// The package where the generated class will live. @@ -50,6 +53,9 @@ class KotlinOptions { /// Kotlin file in the same directory. final bool includeErrorClass; + /// A String to augment class names to avoid cross file collisions. + final String? fileSpecificClassNameComponent; + /// Creates a [KotlinOptions] from a Map representation where: /// `x = KotlinOptions.fromMap(x.toMap())`. static KotlinOptions fromMap(Map map) { @@ -58,6 +64,8 @@ class KotlinOptions { copyrightHeader: map['copyrightHeader'] as Iterable?, errorClassName: map['errorClassName'] as String?, includeErrorClass: map['includeErrorClass'] as bool? ?? true, + fileSpecificClassNameComponent: + map['fileSpecificClassNameComponent'] as String?, ); } @@ -69,6 +77,8 @@ class KotlinOptions { if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!, if (errorClassName != null) 'errorClassName': errorClassName!, 'includeErrorClass': includeErrorClass, + if (fileSpecificClassNameComponent != null) + 'fileSpecificClassNameComponent': fileSpecificClassNameComponent!, }; return result; } @@ -97,6 +107,7 @@ class KotlinGenerator extends StructuredGenerator { } indent.writeln('// ${getGeneratedCodeWarning()}'); indent.writeln('// $seeAlsoWarning'); + indent.writeln('@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")'); } @override @@ -174,7 +185,6 @@ class KotlinGenerator extends StructuredGenerator { addDocumentationComments( indent, classDefinition.documentationComments, _docCommentSpec, generatorComments: generatedMessages); - indent.write('data class ${classDefinition.name} '); indent.addScoped('(', '', () { for (final NamedType element @@ -216,22 +226,12 @@ class KotlinGenerator extends StructuredGenerator { }) { indent.write('fun toList(): List '); indent.addScoped('{', '}', () { - indent.write('return listOf'); + indent.write('return listOf'); indent.addScoped('(', ')', () { for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) { - final HostDatatype hostDatatype = _getHostDatatype(root, field); - String toWriteValue = ''; final String fieldName = field.name; - final String safeCall = field.type.isNullable ? '?' : ''; - if (field.type.isClass) { - toWriteValue = '$fieldName$safeCall.toList()'; - } else if (!hostDatatype.isBuiltin && field.type.isEnum) { - toWriteValue = '$fieldName$safeCall.raw'; - } else { - toWriteValue = fieldName; - } - indent.writeln('$toWriteValue,'); + indent.writeln('$fieldName,'); } }); }); @@ -249,44 +249,16 @@ class KotlinGenerator extends StructuredGenerator { indent.write('companion object '); indent.addScoped('{', '}', () { - indent.writeln('@Suppress("UNCHECKED_CAST")'); - indent.write('fun fromList(list: List): $className '); + indent.writeln('@Suppress("LocalVariableName")'); + indent + .write('fun fromList(${varNamePrefix}list: List): $className '); indent.addScoped('{', '}', () { enumerate(getFieldsInSerializationOrder(classDefinition), (int index, final NamedType field) { - final String listValue = 'list[$index]'; - final String fieldType = _kotlinTypeForDartType(field.type); - - if (field.type.isNullable) { - if (field.type.isClass) { - indent.write('val ${field.name}: $fieldType? = '); - indent.add('($listValue as List?)?.let '); - indent.addScoped('{', '}', () { - indent.writeln('$fieldType.fromList(it)'); - }); - } else if (field.type.isEnum) { - indent.write('val ${field.name}: $fieldType? = '); - indent.add('($listValue as Int?)?.let '); - indent.addScoped('{', '}', () { - indent.writeln('$fieldType.ofRaw(it)'); - }); - } else { - indent.writeln( - 'val ${field.name} = ${_cast(indent, listValue, type: field.type)}'); - } - } else { - if (field.type.isClass) { - indent.writeln( - 'val ${field.name} = $fieldType.fromList($listValue as List)'); - } else if (field.type.isEnum) { - indent.writeln( - 'val ${field.name} = $fieldType.ofRaw($listValue as Int)!!'); - } else { - indent.writeln( - 'val ${field.name} = ${_cast(indent, listValue, type: field.type)}'); - } - } + final String listValue = '${varNamePrefix}list[$index]'; + indent.writeln( + 'val ${field.name} = ${_cast(indent, listValue, type: field.type)}'); }); indent.write('return $className('); @@ -328,6 +300,75 @@ class KotlinGenerator extends StructuredGenerator { dartPackageName: dartPackageName); } + @override + void writeGeneralCodec( + KotlinOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + final Iterable enumeratedTypes = getEnumeratedTypes(root); + indent.write( + 'private object ${generatorOptions.fileSpecificClassNameComponent}$_codecName : StandardMessageCodec() '); + indent.addScoped('{', '}', () { + indent.write( + 'override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? '); + indent.addScoped('{', '}', () { + indent.write('return '); + if (root.classes.isNotEmpty || root.enums.isNotEmpty) { + indent.add('when (type) '); + indent.addScoped('{', '}', () { + for (final EnumeratedType customType in enumeratedTypes) { + indent.write('${customType.enumeration}.toByte() -> '); + indent.addScoped('{', '}', () { + if (customType.type == CustomTypes.customClass) { + indent + .write('return (readValue(buffer) as? List)?.let '); + indent.addScoped('{', '}', () { + indent.writeln('${customType.name}.fromList(it)'); + }); + } else if (customType.type == CustomTypes.customEnum) { + indent.write('return (readValue(buffer) as Int?)?.let '); + indent.addScoped('{', '}', () { + indent.writeln('${customType.name}.ofRaw(it)'); + }); + } + }); + } + indent.writeln('else -> super.readValueOfType(type, buffer)'); + }); + } else { + indent.writeln('super.readValueOfType(type, buffer)'); + } + }); + + indent.write( + 'override fun writeValue(stream: ByteArrayOutputStream, value: Any?) '); + indent.writeScoped('{', '}', () { + if (root.classes.isNotEmpty || root.enums.isNotEmpty) { + indent.write('when (value) '); + indent.addScoped('{', '}', () { + for (final EnumeratedType customType in enumeratedTypes) { + indent.write('is ${customType.name} -> '); + indent.addScoped('{', '}', () { + indent.writeln('stream.write(${customType.enumeration})'); + if (customType.type == CustomTypes.customClass) { + indent.writeln('writeValue(stream, value.toList())'); + } else if (customType.type == CustomTypes.customEnum) { + indent.writeln('writeValue(stream, value.raw)'); + } + }); + } + indent.writeln('else -> super.writeValue(stream, value)'); + }); + } else { + indent.writeln('super.writeValue(stream, value)'); + } + }); + }); + indent.newln(); + } + /// Writes the code for a flutter [Api], [api]. /// Example: /// class Foo(private val binaryMessenger: BinaryMessenger) { @@ -341,11 +382,6 @@ class KotlinGenerator extends StructuredGenerator { AstFlutterApi api, { required String dartPackageName, }) { - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; - if (isCustomCodec) { - _writeCodec(indent, api, root); - } - const List generatedMessages = [ ' Generated class from Pigeon that represents Flutter messages that can be called from Kotlin.' ]; @@ -353,7 +389,6 @@ class KotlinGenerator extends StructuredGenerator { generatorComments: generatedMessages); final String apiName = api.name; - indent.writeln('@Suppress("UNCHECKED_CAST")'); indent.write( 'class $apiName(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") '); indent.addScoped('{', '}', () { @@ -362,11 +397,8 @@ class KotlinGenerator extends StructuredGenerator { indent.writeln('/** The codec used by $apiName. */'); indent.write('val codec: MessageCodec by lazy '); indent.addScoped('{', '}', () { - if (isCustomCodec) { - indent.writeln(_getCodecName(api)); - } else { - indent.writeln('StandardMessageCodec()'); - } + indent.writeln( + '${generatorOptions.fileSpecificClassNameComponent}$_codecName'); }); }); @@ -404,11 +436,6 @@ class KotlinGenerator extends StructuredGenerator { }) { final String apiName = api.name; - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; - if (isCustomCodec) { - _writeCodec(indent, api, root); - } - const List generatedMessages = [ ' Generated interface from Pigeon that represents a handler of messages from Flutter.' ]; @@ -434,15 +461,12 @@ class KotlinGenerator extends StructuredGenerator { indent.writeln('/** The codec used by $apiName. */'); indent.write('val codec: MessageCodec by lazy '); indent.addScoped('{', '}', () { - if (isCustomCodec) { - indent.writeln(_getCodecName(api)); - } else { - indent.writeln('StandardMessageCodec()'); - } + indent.writeln( + '${generatorOptions.fileSpecificClassNameComponent}$_codecName'); }); indent.writeln( '/** Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`. */'); - indent.writeln('@Suppress("UNCHECKED_CAST")'); + indent.writeln('@JvmOverloads'); indent.write( 'fun setUp(binaryMessenger: BinaryMessenger, api: $apiName?, messageChannelSuffix: String = "") '); indent.addScoped('{', '}', () { @@ -465,53 +489,6 @@ class KotlinGenerator extends StructuredGenerator { }); } - /// Writes the codec class that will be used by [api]. - /// Example: - /// private static class FooCodec extends StandardMessageCodec {...} - void _writeCodec(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - final String codecName = _getCodecName(api); - indent.writeln('@Suppress("UNCHECKED_CAST")'); - indent.write('private object $codecName : StandardMessageCodec() '); - indent.addScoped('{', '}', () { - indent.write( - 'override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? '); - indent.addScoped('{', '}', () { - indent.write('return when (type) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('${customClass.enumeration}.toByte() -> '); - indent.addScoped('{', '}', () { - indent.write('return (readValue(buffer) as? List)?.let '); - indent.addScoped('{', '}', () { - indent.writeln('${customClass.name}.fromList(it)'); - }); - }); - } - indent.writeln('else -> super.readValueOfType(type, buffer)'); - }); - }); - - indent.write( - 'override fun writeValue(stream: ByteArrayOutputStream, value: Any?) '); - indent.writeScoped('{', '}', () { - indent.write('when (value) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.write('is ${customClass.name} -> '); - indent.addScoped('{', '}', () { - indent.writeln('stream.write(${customClass.enumeration})'); - indent.writeln('writeValue(stream, value.toList())'); - }); - } - indent.writeln('else -> super.writeValue(stream, value)'); - }); - }); - }); - indent.newln(); - } - void _writeWrapResult(Indent indent) { indent.newln(); indent.write('private fun wrapResult(result: Any?): List '); @@ -524,19 +501,17 @@ class KotlinGenerator extends StructuredGenerator { indent.newln(); indent.write('private fun wrapError(exception: Throwable): List '); indent.addScoped('{', '}', () { - indent - .write('if (exception is ${_getErrorClassName(generatorOptions)}) '); + indent.write( + 'return if (exception is ${_getErrorClassName(generatorOptions)}) '); indent.addScoped('{', '}', () { - indent.write('return '); - indent.addScoped('listOf(', ')', () { + indent.writeScoped('listOf(', ')', () { indent.writeln('exception.code,'); indent.writeln('exception.message,'); indent.writeln('exception.details'); }); }, addTrailingNewline: false); indent.addScoped(' else {', '}', () { - indent.write('return '); - indent.addScoped('listOf(', ')', () { + indent.writeScoped('listOf(', ')', () { indent.writeln('exception.javaClass.simpleName,'); indent.writeln('exception.toString(),'); indent.writeln( @@ -708,47 +683,36 @@ class KotlinGenerator extends StructuredGenerator { : 'api.$name(${methodArguments.join(', ')})'; if (isAsynchronous) { - indent.write('$call '); final String resultType = returnType.isVoid ? 'Unit' : _nullSafeKotlinTypeForDartType(returnType); + indent.write(methodArguments.isNotEmpty ? '$call ' : 'api.$name'); indent.addScoped('{ result: Result<$resultType> ->', '}', () { indent.writeln('val error = result.exceptionOrNull()'); indent.writeScoped('if (error != null) {', '}', () { indent.writeln('reply.reply(wrapError(error))'); }, addTrailingNewline: false); indent.addScoped(' else {', '}', () { - final String enumTagNullablePrefix = - returnType.isNullable ? '?' : '!!'; - final String enumTag = - returnType.isEnum ? '$enumTagNullablePrefix.raw' : ''; if (returnType.isVoid) { indent.writeln('reply.reply(wrapResult(null))'); } else { indent.writeln('val data = result.getOrNull()'); - indent.writeln('reply.reply(wrapResult(data$enumTag))'); + indent.writeln('reply.reply(wrapResult(data))'); } }); }); } else { - indent.writeln('var wrapped: List'); - indent.write('try '); - indent.addScoped('{', '}', () { + indent.writeScoped('val wrapped: List = try {', '}', () { if (returnType.isVoid) { indent.writeln(call); - indent.writeln('wrapped = listOf(null)'); + indent.writeln('listOf(null)'); } else { - String enumTag = ''; - if (returnType.isEnum) { - final String safeUnwrap = returnType.isNullable ? '?' : ''; - enumTag = '$safeUnwrap.raw'; - } - indent.writeln('wrapped = listOf($call$enumTag)'); + indent.writeln('listOf($call)'); } }, addTrailingNewline: false); indent.add(' catch (exception: Throwable) '); indent.addScoped('{', '}', () { - indent.writeln('wrapped = wrapError(exception)'); + indent.writeln('wrapError(exception)'); }); indent.writeln('reply.reply(wrapped)'); } @@ -836,18 +800,10 @@ class KotlinGenerator extends StructuredGenerator { if (returnType.isVoid) { indent.writeln('callback(Result.success(Unit))'); } else { - const String output = 'output'; - // Nullable enums require special handling. - if (returnType.isEnum && returnType.isNullable) { - indent.writeScoped('val $output = (it[0] as Int?)?.let {', '}', - () { - indent.writeln('${returnType.baseName}.ofRaw(it)'); - }); - } else { - indent.writeln( - 'val $output = ${_cast(indent, 'it[0]', type: returnType)}'); - } - indent.writeln('callback(Result.success($output))'); + indent.writeln( + 'val output = ${_cast(indent, 'it[0]', type: returnType)}'); + + indent.writeln('callback(Result.success(output))'); } }); }, addTrailingNewline: false); @@ -859,14 +815,6 @@ class KotlinGenerator extends StructuredGenerator { } } -HostDatatype _getHostDatatype(Root root, NamedType field) { - return getFieldHostDatatype( - field, (TypeDeclaration x) => _kotlinTypeForBuiltinDartType(x)); -} - -/// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; - String _getErrorClassName(KotlinOptions generatorOptions) => generatorOptions.errorClassName ?? 'FlutterError'; @@ -876,11 +824,6 @@ String _getArgumentName(int count, NamedType argument) => /// Returns an argument name that can be used in a context where it is possible to collide /// and append `.index` to enums. String _getEnumSafeArgumentExpression(int count, NamedType argument) { - if (argument.type.isEnum) { - return argument.type.isNullable - ? '${_getArgumentName(count, argument)}Arg?.raw' - : '${_getArgumentName(count, argument)}Arg.raw'; - } return '${_getArgumentName(count, argument)}Arg'; } @@ -889,14 +832,7 @@ String _getSafeArgumentName(int count, NamedType argument) => '${_getArgumentName(count, argument)}Arg'; String _castForceUnwrap(String value, TypeDeclaration type, Indent indent) { - if (type.isEnum) { - final String forceUnwrap = type.isNullable ? '' : '!!'; - final String nullableConditionPrefix = - type.isNullable ? 'if ($value == null) null else ' : ''; - return '$nullableConditionPrefix${_kotlinTypeForDartType(type)}.ofRaw($value as Int)$forceUnwrap'; - } else { - return _cast(indent, value, type: type); - } + return _cast(indent, value, type: type); } /// Converts a [List] of [TypeDeclaration]s to a comma separated [String] to be @@ -969,18 +905,10 @@ String _cast(Indent indent, String variable, {required TypeDeclaration type}) { if (typeString == 'Int' || typeString == 'Long') { return '$variable${_castInt(type.isNullable)}'; } - if (type.isEnum) { - if (type.isNullable) { - return '($variable as Int?)?.let {\n' - '${indent.str} $typeString.ofRaw(it)\n' - '${indent.str}}'; - } - return '${type.baseName}.ofRaw($variable as Int)!!'; - } return '$variable as ${_nullSafeKotlinTypeForDartType(type)}'; } String _castInt(bool isNullable) { final String nullability = isNullable ? '?' : ''; - return '.let { if (it is Int) it.toLong() else it as Long$nullability }'; + return '.let { num -> if (num is Int) num.toLong() else num as Long$nullability }'; } diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index c40869a9b24..e378fbac1c2 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -22,6 +22,7 @@ class ObjcOptions { this.headerIncludePath, this.prefix, this.copyrightHeader, + this.fileSpecificClassNameComponent, }); /// The path to the header that will get placed in the source filed (example: @@ -34,15 +35,20 @@ class ObjcOptions { /// A copyright header that will get prepended to generated code. final Iterable? copyrightHeader; + /// A String to augment class names to avoid cross file collisions. + final String? fileSpecificClassNameComponent; + /// Creates a [ObjcOptions] from a Map representation where: /// `x = ObjcOptions.fromMap(x.toMap())`. static ObjcOptions fromMap(Map map) { final Iterable? copyrightHeader = map['copyrightHeader'] as Iterable?; return ObjcOptions( - headerIncludePath: map['header'] as String?, + headerIncludePath: map['headerIncludePath'] as String?, prefix: map['prefix'] as String?, copyrightHeader: copyrightHeader?.cast(), + fileSpecificClassNameComponent: + map['fileSpecificClassNameComponent'] as String?, ); } @@ -50,9 +56,11 @@ class ObjcOptions { /// `x = ObjcOptions.fromMap(x.toMap())`. Map toMap() { final Map result = { - if (headerIncludePath != null) 'header': headerIncludePath!, + if (headerIncludePath != null) 'headerIncludePath': headerIncludePath!, if (prefix != null) 'prefix': prefix!, if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!, + if (fileSpecificClassNameComponent != null) + 'fileSpecificClassNameComponent': fileSpecificClassNameComponent!, }; return result; } @@ -199,8 +207,6 @@ class ObjcHeaderGenerator extends StructuredGenerator { Class classDefinition, { required String dartPackageName, }) { - final List classes = root.classes; - final List enums = root.enums; final String? prefix = generatorOptions.prefix; addDocumentationComments( @@ -221,8 +227,6 @@ class ObjcHeaderGenerator extends StructuredGenerator { generatorOptions, root, classDefinition, - classes, - enums, prefix, ); indent.addln(';'); @@ -273,6 +277,18 @@ class ObjcHeaderGenerator extends StructuredGenerator { required String dartPackageName, }) {} + @override + void writeGeneralCodec( + ObjcOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + indent.writeln('$_docCommentPrefix The codec used by all APIs.'); + indent.writeln( + 'NSObject *${generatorOptions.prefix}Get${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}Codec(void);'); + } + @override void writeApis( ObjcOptions generatorOptions, @@ -293,10 +309,6 @@ class ObjcHeaderGenerator extends StructuredGenerator { Api api, { required String dartPackageName, }) { - indent.writeln( - '$_docCommentPrefix The codec used by ${_className(generatorOptions.prefix, api.name)}.'); - indent.writeln( - 'NSObject *${_getCodecGetterName(generatorOptions.prefix, api.name)}(void);'); indent.newln(); final String apiName = _className(generatorOptions.prefix, api.name); addDocumentationComments( @@ -338,10 +350,6 @@ class ObjcHeaderGenerator extends StructuredGenerator { Api api, { required String dartPackageName, }) { - indent.writeln( - '$_docCommentPrefix The codec used by ${_className(generatorOptions.prefix, api.name)}.'); - indent.writeln( - 'NSObject *${_getCodecGetterName(generatorOptions.prefix, api.name)}(void);'); indent.newln(); final String apiName = _className(generatorOptions.prefix, api.name); addDocumentationComments( @@ -552,12 +560,12 @@ class ObjcSourceGenerator extends StructuredGenerator { Class classDefinition, { required String dartPackageName, }) { - indent.write('- (NSArray *)toList '); + indent.write('- (NSArray *)toList '); indent.addScoped('{', '}', () { indent.write('return'); indent.addScoped(' @[', '];', () { for (final NamedType field in classDefinition.fields) { - indent.writeln('${_arrayValue(field)},'); + indent.writeln('${_arrayValue(field, generatorOptions.prefix)},'); } }); }); @@ -573,25 +581,26 @@ class ObjcSourceGenerator extends StructuredGenerator { }) { final String className = _className(generatorOptions.prefix, classDefinition.name); - indent.write('+ ($className *)fromList:(NSArray *)list '); + indent.write('+ ($className *)fromList:(NSArray *)list '); indent.addScoped('{', '}', () { const String resultName = 'pigeonResult'; indent.writeln('$className *$resultName = [[$className alloc] init];'); enumerate(getFieldsInSerializationOrder(classDefinition), (int index, final NamedType field) { - final bool isEnumType = field.type.isEnum; - final String valueGetter = - _listGetter('list', field, index, generatorOptions.prefix); + final String valueGetter = 'GetNullableObjectAtIndex(list, $index)'; final String? primitiveExtractionMethod = _nsnumberExtractionMethod(field.type); final String ivarValueExpression; - if (primitiveExtractionMethod != null) { + if (field.type.isEnum && !field.type.isNullable) { + _writeEnumBoxToEnum( + indent, + field, + valueGetter, + prefix: generatorOptions.prefix, + ); + ivarValueExpression = 'enumBox.value'; + } else if (primitiveExtractionMethod != null) { ivarValueExpression = '[$valueGetter $primitiveExtractionMethod]'; - } else if (isEnumType) { - indent.writeln('NSNumber *${field.name}AsNumber = $valueGetter;'); - 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]];'); - ivarValueExpression = field.name; } else { ivarValueExpression = valueGetter; } @@ -600,21 +609,124 @@ class ObjcSourceGenerator extends StructuredGenerator { indent.writeln('return $resultName;'); }); - indent.write('+ (nullable $className *)nullableFromList:(NSArray *)list '); + indent.write( + '+ (nullable $className *)nullableFromList:(NSArray *)list '); indent.addScoped('{', '}', () { indent.writeln('return (list) ? [$className fromList:list] : nil;'); }); } - void _writeCodecAndGetter( - ObjcOptions generatorOptions, Root root, Indent indent, Api api) { - final String codecName = _getCodecName(generatorOptions.prefix, api.name); - if (getCodecClasses(api, root).isNotEmpty) { - _writeCodec(indent, codecName, generatorOptions, api, root); - indent.newln(); - } - _writeCodecGetter(indent, codecName, generatorOptions, api, root); + @override + void writeGeneralCodec( + ObjcOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + const String codecName = 'PigeonCodec'; + final Iterable codecClasses = getEnumeratedTypes(root); + final String readerWriterName = + '${generatorOptions.prefix}${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}${codecName}ReaderWriter'; + final String readerName = + '${generatorOptions.prefix}${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}${codecName}Reader'; + final String writerName = + '${generatorOptions.prefix}${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}${codecName}Writer'; + indent.writeln('@interface $readerName : FlutterStandardReader'); + indent.writeln('@end'); + indent.writeln('@implementation $readerName'); + indent.write('- (nullable id)readValueOfType:(UInt8)type '); + indent.addScoped('{', '}', () { + indent.write('switch (type) '); + indent.addScoped('{', '}', () { + for (final EnumeratedType customType in codecClasses) { + indent.writeln('case ${customType.enumeration}: '); + indent.nest(1, () { + if (customType.type == CustomTypes.customClass) { + indent.writeln( + 'return [${_className(generatorOptions.prefix, customType.name)} fromList:[self readValue]];'); + } else if (customType.type == CustomTypes.customEnum) { + indent.writeScoped('{', '}', () { + indent.writeln('NSNumber *enumAsNumber = [self readValue];'); + indent.writeln( + 'return enumAsNumber == nil ? nil : [[${_enumName(customType.name, prefix: generatorOptions.prefix, box: true)} alloc] initWithValue:[enumAsNumber integerValue]];'); + }); + } + }); + } + indent.writeln('default:'); + indent.nest(1, () { + indent.writeln('return [super readValueOfType:type];'); + }); + }); + }); + indent.writeln('@end'); indent.newln(); + indent.writeln('@interface $writerName : FlutterStandardWriter'); + indent.writeln('@end'); + indent.writeln('@implementation $writerName'); + indent.write('- (void)writeValue:(id)value '); + indent.addScoped('{', '}', () { + bool firstClass = true; + for (final EnumeratedType customClass in codecClasses) { + if (firstClass) { + indent.write(''); + firstClass = false; + } + if (customClass.type == CustomTypes.customClass) { + indent.add( + 'if ([value isKindOfClass:[${_className(generatorOptions.prefix, customClass.name)} class]]) '); + indent.addScoped('{', '} else ', () { + indent.writeln('[self writeByte:${customClass.enumeration}];'); + indent.writeln('[self writeValue:[value toList]];'); + }, addTrailingNewline: false); + } else if (customClass.type == CustomTypes.customEnum) { + final String boxName = _enumName(customClass.name, + prefix: generatorOptions.prefix, box: true); + indent.add('if ([value isKindOfClass:[$boxName class]]) '); + indent.addScoped('{', '} else ', () { + indent.writeln('$boxName * box = ($boxName *)value;'); + indent.writeln('[self writeByte:${customClass.enumeration}];'); + indent.writeln( + '[self writeValue:(value == nil ? [NSNull null] : [NSNumber numberWithInteger:box.value])];'); + }, addTrailingNewline: false); + } + } + indent.addScoped('{', '}', () { + indent.writeln('[super writeValue:value];'); + }); + }); + indent.writeln('@end'); + indent.newln(); + indent.format(''' +@interface $readerWriterName : FlutterStandardReaderWriter +@end +@implementation $readerWriterName +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { +\treturn [[$writerName alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { +\treturn [[$readerName alloc] initWithData:data]; +} +@end'''); + indent.newln(); + + indent.write( + 'NSObject *${generatorOptions.prefix}Get${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}Codec(void) '); + indent.addScoped('{', '}', () { + indent + .writeln('static FlutterStandardMessageCodec *sSharedObject = nil;'); + + indent.writeln('static dispatch_once_t sPred = 0;'); + indent.write('dispatch_once(&sPred, ^'); + indent.addScoped('{', '});', () { + indent.writeln( + '$readerWriterName *readerWriter = [[$readerWriterName alloc] init];'); + indent.writeln( + 'sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter];'); + }); + + indent.writeln('return sSharedObject;'); + }); } @override @@ -627,8 +739,6 @@ class ObjcSourceGenerator extends StructuredGenerator { }) { final String apiName = _className(generatorOptions.prefix, api.name); - _writeCodecAndGetter(generatorOptions, root, indent, api); - _writeExtension(indent, apiName); indent.newln(); indent.writeln('@implementation $apiName'); @@ -658,8 +768,6 @@ class ObjcSourceGenerator extends StructuredGenerator { }) { final String apiName = _className(generatorOptions.prefix, api.name); - _writeCodecAndGetter(generatorOptions, root, indent, api); - const String channelName = 'channel'; indent.write( 'void SetUp$apiName(id binaryMessenger, NSObject<$apiName> *api) '); @@ -736,7 +844,7 @@ class ObjcSourceGenerator extends StructuredGenerator { void _writeWrapError(Indent indent) { indent.format(''' -static NSArray *wrapResult(id result, FlutterError *error) { +static NSArray *wrapResult(id result, FlutterError *error) { \tif (error) { \t\treturn @[ \t\t\terror.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] @@ -748,7 +856,7 @@ static NSArray *wrapResult(id result, FlutterError *error) { void _writeGetNullableObjectAtIndex(Indent indent) { indent.format(''' -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { \tid result = array[key]; \treturn (result == [NSNull null]) ? nil : result; }'''); @@ -764,7 +872,7 @@ static FlutterError *createConnectionError(NSString *channelName) { void _writeChannelApiBinding(ObjcOptions generatorOptions, Root root, Indent indent, String apiName, Method func, String channel) { void unpackArgs(String variable) { - indent.writeln('NSArray *args = $variable;'); + indent.writeln('NSArray *args = $variable;'); int count = 0; for (final NamedType arg in func.parameters) { final String argName = _getSafeArgName(count, arg); @@ -775,16 +883,30 @@ static FlutterError *createConnectionError(NSString *channelName) { generatorOptions.prefix, arg.type, ); - if (primitiveExtractionMethod != null) { - indent.writeln( - '${objcArgType.beforeString}$argName = [$valueGetter $primitiveExtractionMethod];'); - } else if (arg.type.isEnum) { - indent.writeln('NSNumber *${argName}AsNumber = $valueGetter;'); - indent.writeln( - '${_enumName(arg.type.baseName, suffix: ' *', prefix: generatorOptions.prefix, box: true)}$argName = ${argName}AsNumber == nil ? nil : [[${_enumName(arg.type.baseName, prefix: generatorOptions.prefix, box: true)} alloc] initWithValue:[${argName}AsNumber integerValue]];'); + final String ivarValueExpression; + String beforeString = objcArgType.beforeString; + if (arg.type.isEnum && !arg.type.isNullable) { + _writeEnumBoxToEnum( + indent, + arg, + valueGetter, + prefix: generatorOptions.prefix, + ); + ivarValueExpression = 'enumBox.value'; + } else if (primitiveExtractionMethod != null) { + ivarValueExpression = '[$valueGetter $primitiveExtractionMethod]'; } else { - indent.writeln('${objcArgType.beforeString}$argName = $valueGetter;'); + if (arg.type.isEnum) { + beforeString = _enumName( + arg.type.baseName, + prefix: generatorOptions.prefix, + box: true, + suffix: ' *', + ); + } + ivarValueExpression = valueGetter; } + indent.writeln('$beforeString$argName = $ivarValueExpression;'); count++; } } @@ -809,30 +931,21 @@ static FlutterError *createConnectionError(NSString *channelName) { } else { const String callback = 'callback(wrapResult(output, error));'; String returnTypeString = '${returnType.beforeString}_Nullable output'; - const String numberOutput = 'NSNumber *output ='; - const String enumConversionExpression = - 'enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value];'; if (func.returnType.isEnum) { returnTypeString = - '${_enumName(func.returnType.baseName, suffix: ' *_Nullable', prefix: generatorOptions.prefix, box: true)} enumValue'; + '${_enumName(func.returnType.baseName, suffix: ' *_Nullable', prefix: generatorOptions.prefix, box: true)} output'; } if (func.parameters.isEmpty) { indent.writeScoped( '[api ${selectorComponents.first}:^($returnTypeString, FlutterError *_Nullable error) {', '}];', () { - if (func.returnType.isEnum) { - indent.writeln('$numberOutput $enumConversionExpression'); - } indent.writeln(callback); }); } else { indent.writeScoped( '[api $callSignature ${selectorComponents.last}:^($returnTypeString, FlutterError *_Nullable error) {', '}];', () { - if (func.returnType.isEnum) { - indent.writeln('$numberOutput $enumConversionExpression'); - } indent.writeln(callback); }); } @@ -847,9 +960,7 @@ static FlutterError *createConnectionError(NSString *channelName) { } else { if (func.returnType.isEnum) { indent.writeln( - '${_enumName(func.returnType.baseName, suffix: ' *', prefix: generatorOptions.prefix, box: true)} enumBox = $call;'); - indent.writeln( - 'NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value];'); + '${_enumName(func.returnType.baseName, suffix: ' *', prefix: generatorOptions.prefix, box: true)} output = $call;'); } else { indent.writeln('${returnType.beforeString}output = $call;'); } @@ -911,8 +1022,8 @@ static FlutterError *createConnectionError(NSString *channelName) { 'initWithName:[NSString stringWithFormat:@"%@%@", @"${makeChannelName(api, func, dartPackageName)}", messageChannelSuffix]'); indent.writeln('binaryMessenger:binaryMessenger'); indent.write('codec:'); - indent - .add('${_getCodecGetterName(generatorOptions.prefix, api.name)}()'); + indent.add( + '${generatorOptions.prefix}Get${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}Codec()'); if (taskQueue != null) { indent.newln(); @@ -930,10 +1041,10 @@ static FlutterError *createConnectionError(NSString *channelName) { _className(languageOptions.prefix, classDefinition.name); indent.newln(); indent.writeln('@interface $className ()'); - indent.writeln('+ ($className *)fromList:(NSArray *)list;'); - indent - .writeln('+ (nullable $className *)nullableFromList:(NSArray *)list;'); - indent.writeln('- (NSArray *)toList;'); + indent.writeln('+ ($className *)fromList:(NSArray *)list;'); + indent.writeln( + '+ (nullable $className *)nullableFromList:(NSArray *)list;'); + indent.writeln('- (NSArray *)toList;'); indent.writeln('@end'); } @@ -951,8 +1062,6 @@ static FlutterError *createConnectionError(NSString *channelName) { languageOptions, root, classDefinition, - root.classes, - root.enums, languageOptions.prefix, ); indent.writeScoped(' {', '}', () { @@ -965,222 +1074,116 @@ static FlutterError *createConnectionError(NSString *channelName) { indent.writeln('return $result;'); }); } - - /// Writes the codec that will be used for encoding messages for the [api]. - /// - /// Example: - /// @interface FooHostApiCodecReader : FlutterStandardReader - /// ... - /// @interface FooHostApiCodecWriter : FlutterStandardWriter - /// ... - /// @interface FooHostApiCodecReaderWriter : FlutterStandardReaderWriter - /// ... - /// NSObject *FooHostApiCodecGetCodec(void) {...} - void _writeCodec( - Indent indent, String name, ObjcOptions options, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final Iterable codecClasses = getCodecClasses(api, root); - final String readerWriterName = '${name}ReaderWriter'; - final String readerName = '${name}Reader'; - final String writerName = '${name}Writer'; - indent.writeln('@interface $readerName : FlutterStandardReader'); - indent.writeln('@end'); - indent.writeln('@implementation $readerName'); - indent.write('- (nullable id)readValueOfType:(UInt8)type '); - indent.addScoped('{', '}', () { - indent.write('switch (type) '); - indent.addScoped('{', '}', () { - for (final EnumeratedClass customClass in codecClasses) { - indent.writeln('case ${customClass.enumeration}: '); - indent.nest(1, () { - indent.writeln( - 'return [${_className(options.prefix, customClass.name)} fromList:[self readValue]];'); - }); - } - indent.writeln('default:'); - indent.nest(1, () { - indent.writeln('return [super readValueOfType:type];'); - }); - }); - }); - indent.writeln('@end'); - indent.newln(); - indent.writeln('@interface $writerName : FlutterStandardWriter'); - indent.writeln('@end'); - indent.writeln('@implementation $writerName'); - indent.write('- (void)writeValue:(id)value '); - indent.addScoped('{', '}', () { - bool firstClass = true; - for (final EnumeratedClass customClass in codecClasses) { - if (firstClass) { - indent.write(''); - firstClass = false; - } - indent.add( - 'if ([value isKindOfClass:[${_className(options.prefix, customClass.name)} class]]) '); - indent.addScoped('{', '} else ', () { - indent.writeln('[self writeByte:${customClass.enumeration}];'); - indent.writeln('[self writeValue:[value toList]];'); - }, addTrailingNewline: false); - } - indent.addScoped('{', '}', () { - indent.writeln('[super writeValue:value];'); - }); - }); - indent.writeln('@end'); - indent.newln(); - indent.format(''' -@interface $readerWriterName : FlutterStandardReaderWriter -@end -@implementation $readerWriterName -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { -\treturn [[$writerName alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { -\treturn [[$readerName alloc] initWithData:data]; } -@end'''); - } - void _writeCodecGetter( - Indent indent, String name, ObjcOptions options, Api api, Root root) { - final String readerWriterName = '${name}ReaderWriter'; +void _writeMethod( + ObjcOptions languageOptions, + Root root, + Indent indent, + Api api, + Method func, { + required String dartPackageName, +}) { + 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(func.returnType, returnType, languageOptions); - indent.write( - 'NSObject *${_getCodecGetterName(options.prefix, api.name)}(void) '); - indent.addScoped('{', '}', () { - indent - .writeln('static FlutterStandardMessageCodec *sSharedObject = nil;'); - if (getCodecClasses(api, root).isNotEmpty) { - indent.writeln('static dispatch_once_t sPred = 0;'); - indent.write('dispatch_once(&sPred, ^'); - indent.addScoped('{', '});', () { - indent.writeln( - '$readerWriterName *readerWriter = [[$readerWriterName alloc] init];'); - indent.writeln( - 'sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter];'); - }); - } else { - indent.writeln( - 'sSharedObject = [FlutterStandardMessageCodec sharedInstance];'); + String argNameFunc(int count, NamedType arg) => _getSafeArgName(count, arg); + String sendArgument; + if (func.parameters.isEmpty) { + sendArgument = 'nil'; + } else { + int count = 0; + String makeVarOrNSNullExpression(NamedType arg) { + final String argName = argNameFunc(count, arg); + String varExpression = _collectionSafeExpression(argName, arg.type); + if (arg.type.isEnum) { + if (arg.type.isNullable) { + varExpression = + '${argNameFunc(count, arg)} == nil ? [NSNull null] : $argName'; + } else { + varExpression = _getEnumToEnumBox(arg, argNameFunc(count, arg), + prefix: languageOptions.prefix); + } } + count++; + return varExpression; + } - indent.writeln('return sSharedObject;'); - }); + sendArgument = + '@[${func.parameters.map(makeVarOrNSNullExpression).join(', ')}]'; } + indent.write(_makeObjcSignature( + func: func, + options: languageOptions, + returnType: 'void', + lastArgName: 'completion', + lastArgType: callbackType, + argNameFunc: argNameFunc, + )); + indent.addScoped(' {', '}', () { + indent.writeln( + 'NSString *channelName = [NSString stringWithFormat:@"%@%@", @"${makeChannelName(api, func, dartPackageName)}", _messageChannelSuffix];'); + indent.writeln('FlutterBasicMessageChannel *channel ='); - void _writeMethod( - ObjcOptions languageOptions, - Root root, - Indent indent, - Api api, - Method func, { - required String dartPackageName, - }) { - 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(func.returnType, returnType, languageOptions); - - String argNameFunc(int count, NamedType arg) => _getSafeArgName(count, arg); - String sendArgument; - if (func.parameters.isEmpty) { - sendArgument = 'nil'; - } else { - int count = 0; - String makeVarOrNSNullExpression(NamedType arg) { - final String argName = argNameFunc(count, arg); - String varExpression = _collectionSafeExpression(argName, arg.type); - if (arg.type.isEnum) { - if (arg.type.isNullable) { - varExpression = - '${argNameFunc(count, arg)} == nil ? [NSNull null] : [NSNumber numberWithInteger:$argName.value]'; - } else { - varExpression = '[NSNumber numberWithInteger:$argName]'; - } - } - count++; - return varExpression; - } - - sendArgument = - '@[${func.parameters.map(makeVarOrNSNullExpression).join(', ')}]'; - } - indent.write(_makeObjcSignature( - func: func, - options: languageOptions, - returnType: 'void', - lastArgName: 'completion', - lastArgType: callbackType, - argNameFunc: argNameFunc, - )); - indent.addScoped(' {', '}', () { - indent.writeln( - 'NSString *channelName = [NSString stringWithFormat:@"%@%@", @"${makeChannelName(api, func, dartPackageName)}", _messageChannelSuffix];'); - indent.writeln('FlutterBasicMessageChannel *channel ='); + indent.nest(1, () { + indent.writeln('[FlutterBasicMessageChannel'); indent.nest(1, () { - indent.writeln('[FlutterBasicMessageChannel'); - indent.nest(1, () { - indent.writeln('messageChannelWithName:channelName'); - indent.writeln('binaryMessenger:self.binaryMessenger'); - indent.write( - 'codec:${_getCodecGetterName(languageOptions.prefix, api.name)}()'); - indent.addln('];'); - }); + indent.writeln('messageChannelWithName:channelName'); + indent.writeln('binaryMessenger:self.binaryMessenger'); + indent.write( + 'codec:${languageOptions.prefix}Get${toUpperCamelCase(languageOptions.fileSpecificClassNameComponent ?? '')}Codec()'); + indent.addln('];'); }); - final String valueOnErrorResponse = func.returnType.isVoid ? '' : 'nil, '; - indent.write( - '[channel sendMessage:$sendArgument reply:^(NSArray *reply) '); - indent.addScoped('{', '}];', () { - indent.writeScoped('if (reply != nil) {', '} ', () { - indent.writeScoped('if (reply.count > 1) {', '} ', () { - indent.writeln( - 'completion($valueOnErrorResponse[FlutterError errorWithCode:reply[0] message:reply[1] details:reply[2]]);'); - }, addTrailingNewline: false); - indent.addScoped('else {', '}', () { - const String nullCheck = - 'reply[0] == [NSNull null] ? nil : reply[0]'; - if (func.returnType.isVoid) { - indent.writeln('completion(nil);'); + }); + final String valueOnErrorResponse = func.returnType.isVoid ? '' : 'nil, '; + indent.write( + '[channel sendMessage:$sendArgument reply:^(NSArray *reply) '); + indent.addScoped('{', '}];', () { + indent.writeScoped('if (reply != nil) {', '} ', () { + indent.writeScoped('if (reply.count > 1) {', '} ', () { + indent.writeln( + 'completion($valueOnErrorResponse[FlutterError errorWithCode:reply[0] message:reply[1] details:reply[2]]);'); + }, addTrailingNewline: false); + indent.addScoped('else {', '}', () { + const String nullCheck = 'reply[0] == [NSNull null] ? nil : reply[0]'; + if (func.returnType.isVoid) { + indent.writeln('completion(nil);'); + } else { + if (func.returnType.isEnum) { + final String enumName = _enumName(func.returnType.baseName, + prefix: languageOptions.prefix, box: true); + indent.writeln('$enumName *output = $nullCheck;'); } else { - if (func.returnType.isEnum) { - final String enumName = _enumName(func.returnType.baseName, - prefix: languageOptions.prefix, box: true); - indent.writeln('NSNumber *outputAsNumber = $nullCheck;'); - indent.writeln( - '$enumName *output = outputAsNumber == nil ? nil : [[$enumName alloc] initWithValue:[outputAsNumber integerValue]];'); - } else { - indent - .writeln('${returnType.beforeString}output = $nullCheck;'); - } - indent.writeln('completion(output, nil);'); + indent.writeln('${returnType.beforeString}output = $nullCheck;'); } - }); - }, addTrailingNewline: false); - indent.addScoped('else {', '} ', () { - indent.writeln( - 'completion(${valueOnErrorResponse}createConnectionError(channelName));'); + indent.writeln('completion(output, nil);'); + } }); + }, addTrailingNewline: false); + indent.addScoped('else {', '} ', () { + indent.writeln( + 'completion(${valueOnErrorResponse}createConnectionError(channelName));'); }); }); - } + }); } /// Writes the method declaration for the initializer. /// /// Example '+ (instancetype)makeWithFoo:(NSString *)foo' void _writeObjcSourceClassInitializerDeclaration( - Indent indent, - ObjcOptions generatorOptions, - Root root, - Class classDefinition, - List classes, - List enums, - String? prefix) { + Indent indent, + ObjcOptions generatorOptions, + Root root, + Class classDefinition, + String? prefix, +) { indent.write('+ (instancetype)makeWith'); bool isFirst = true; indent.nest(2, () { @@ -1243,7 +1246,10 @@ class _ObjcType { final bool hasAsterisk; @override - String toString() => hasAsterisk ? '$baseName *' : baseName; + String toString() => + hasAsterisk ? '$baseName$listGenericTag *' : '$baseName$listGenericTag'; + + String get listGenericTag => baseName == 'NSArray' ? '' : ''; /// 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 @@ -1334,10 +1340,14 @@ String? _nsnumberExtractionMethod( /// 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).toString()) - .join(', '); + final String result = args.map((TypeDeclaration e) { + // print(e); + if (e.isEnum) { + return _enumName(e.baseName, + prefix: classPrefix, box: true, suffix: ' *'); + } + return _objcTypeForDartType(classPrefix, e).toString(); + }).join(', '); return result; } @@ -1406,15 +1416,6 @@ String _propertyTypeForDartType(TypeDeclaration type, return 'strong'; } -/// Generates the name of the codec that will be generated. -String _getCodecName(String? prefix, String className) => - '${_className(prefix, className)}Codec'; - -/// Generates the name of the function for accessing the codec instance used by -/// the api class named [className]. -String _getCodecGetterName(String? prefix, String className) => - '${_className(prefix, className)}GetCodec'; - String _capitalize(String str) => (str.isEmpty) ? '' : str[0].toUpperCase() + str.substring(1); @@ -1500,26 +1501,9 @@ String _makeObjcSignature({ /// provided [options]. void generateObjcHeader(ObjcOptions options, Root root, Indent indent) {} -String _listGetter(String list, NamedType field, int index, String? prefix) { - if (field.type.isClass) { - String className = field.type.baseName; - if (prefix != null) { - className = '$prefix$className'; - } - return '[$className nullableFromList:(GetNullableObjectAtIndex($list, $index))]'; - } else { - return 'GetNullableObjectAtIndex($list, $index)'; - } -} - -String _arrayValue(NamedType field) { - if (field.type.isClass) { - return '(self.${field.name} ? [self.${field.name} toList] : [NSNull null])'; - } else if (field.type.isEnum) { - if (field.type.isNullable) { - return '(self.${field.name} == nil ? [NSNull null] : [NSNumber numberWithInteger:self.${field.name}.value])'; - } - return '@(self.${field.name})'; +String _arrayValue(NamedType field, String? prefix) { + if (field.type.isEnum && !field.type.isNullable) { + return _getEnumToEnumBox(field, 'self.${field.name}', prefix: prefix); } else { return _collectionSafeExpression( 'self.${field.name}', @@ -1584,3 +1568,21 @@ List validateObjc(ObjcOptions options, Root root) { return errors; } + +void _writeEnumBoxToEnum( + Indent indent, + NamedType field, + String valueGetter, { + String? prefix = '', +}) { + indent.writeln( + '${_enumName(field.type.baseName, prefix: prefix, box: true, suffix: ' *')}enumBox = $valueGetter;'); +} + +String _getEnumToEnumBox( + NamedType field, + String valueSetter, { + String? prefix = '', +}) { + return '[[${_enumName(field.type.baseName, prefix: prefix, box: true)} alloc] initWithValue:$valueSetter]'; +} diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index addce55d54a..2592dd24c55 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -394,7 +394,7 @@ class PigeonOptions { if (oneLanguage != null) 'oneLanguage': oneLanguage!, if (debugGenerators != null) 'debugGenerators': debugGenerators!, if (basePath != null) 'basePath': basePath!, - if (_dartPackageName != null) 'dartPackageName': _dartPackageName!, + if (_dartPackageName != null) 'dartPackageName': _dartPackageName, }; return result; } @@ -621,6 +621,12 @@ class ObjcGeneratorAdapter implements GeneratorAdapter { StringSink sink, PigeonOptions options, Root root, FileType fileType) { final ObjcOptions objcOptions = options.objcOptions ?? const ObjcOptions(); final ObjcOptions objcOptionsWithHeader = objcOptions.merge(ObjcOptions( + fileSpecificClassNameComponent: options.objcSourceOut + ?.split('/') + .lastOrNull + ?.split('.') + .firstOrNull ?? + '', copyrightHeader: options.copyrightHeader != null ? _lineReader( path.posix.join(options.basePath ?? '', options.copyrightHeader)) @@ -701,10 +707,13 @@ class SwiftGeneratorAdapter implements GeneratorAdapter { StringSink sink, PigeonOptions options, Root root, FileType fileType) { SwiftOptions swiftOptions = options.swiftOptions ?? const SwiftOptions(); swiftOptions = swiftOptions.merge(SwiftOptions( + fileSpecificClassNameComponent: + options.swiftOut?.split('/').lastOrNull?.split('.').firstOrNull ?? '', copyrightHeader: options.copyrightHeader != null ? _lineReader( path.posix.join(options.basePath ?? '', options.copyrightHeader)) : null, + errorClassName: swiftOptions.errorClassName, )); const SwiftGenerator generator = SwiftGenerator(); generator.generate( @@ -783,6 +792,9 @@ class KotlinGeneratorAdapter implements GeneratorAdapter { kotlinOptions = kotlinOptions.merge(KotlinOptions( errorClassName: kotlinOptions.errorClassName ?? 'FlutterError', includeErrorClass: kotlinOptions.includeErrorClass, + fileSpecificClassNameComponent: + options.kotlinOut?.split('/').lastOrNull?.split('.').firstOrNull ?? + '', copyrightHeader: options.copyrightHeader != null ? _lineReader( path.posix.join(options.basePath ?? '', options.copyrightHeader)) @@ -1345,12 +1357,20 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { (Api apiDefinition) => apiDefinition.name == type.baseName, ); if (assocClass != null) { - return type.copyWithClass(assocClass); + type = type.copyWithClass(assocClass); } else if (assocEnum != null) { - return type.copyWithEnum(assocEnum); + type = type.copyWithEnum(assocEnum); } else if (assocProxyApi != null) { - return type.copyWithProxyApi(assocProxyApi); + type = type.copyWithProxyApi(assocProxyApi); } + if (type.typeArguments.isNotEmpty) { + final List newTypes = []; + for (final TypeDeclaration type in type.typeArguments) { + newTypes.add(_attachAssociatedDefinition(type)); + } + type = type.copyWithTypeArguments(newTypes); + } + return type; } @@ -1980,7 +2000,7 @@ class Pigeon { } /// Reads the file located at [path] and generates [ParseResults] by parsing - /// it. [types] optionally filters out what datatypes are actually parsed. + /// it. [types] optionally filters out what datatypes are actually parsed. /// [sdkPath] for specifying the Dart SDK path for /// [AnalysisContextCollection]. ParseResults parseFile(String inputPath, {String? sdkPath}) { @@ -2248,14 +2268,16 @@ ${_argParser.usage}'''; options = options.merge(PigeonOptions( objcOptions: (options.objcOptions ?? const ObjcOptions()).merge( ObjcOptions( - headerIncludePath: path.basename(options.objcHeaderOut!))))); + headerIncludePath: options.objcOptions?.headerIncludePath ?? + path.basename(options.objcHeaderOut!))))); } if (options.cppHeaderOut != null) { options = options.merge(PigeonOptions( cppOptions: (options.cppOptions ?? const CppOptions()).merge( CppOptions( - headerIncludePath: path.basename(options.cppHeaderOut!))))); + headerIncludePath: options.cppOptions?.headerIncludePath ?? + path.basename(options.cppHeaderOut!))))); } for (final GeneratorAdapter adapter in safeGeneratorAdapters) { diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 854c2c835f2..99ad77f39e7 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -19,16 +19,27 @@ class SwiftOptions { /// Creates a [SwiftOptions] object const SwiftOptions({ this.copyrightHeader, + this.fileSpecificClassNameComponent, + this.errorClassName, }); /// A copyright header that will get prepended to generated code. final Iterable? copyrightHeader; + /// A String to augment class names to avoid cross file collisions. + final String? fileSpecificClassNameComponent; + + /// The name of the error class used for passing custom error parameters. + final String? errorClassName; + /// Creates a [SwiftOptions] from a Map representation where: /// `x = SwiftOptions.fromList(x.toMap())`. static SwiftOptions fromList(Map map) { return SwiftOptions( copyrightHeader: map['copyrightHeader'] as Iterable?, + fileSpecificClassNameComponent: + map['fileSpecificClassNameComponent'] as String?, + errorClassName: map['errorClassName'] as String?, ); } @@ -37,6 +48,9 @@ class SwiftOptions { Map toMap() { final Map result = { if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!, + if (fileSpecificClassNameComponent != null) + 'fileSpecificClassNameComponent': fileSpecificClassNameComponent!, + if (errorClassName != null) 'errorClassName': errorClassName!, }; return result; } @@ -109,6 +123,110 @@ class SwiftGenerator extends StructuredGenerator { }); } + @override + void writeGeneralCodec( + SwiftOptions generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) { + final String codecName = _getCodecName(generatorOptions); + final String readerWriterName = '${codecName}ReaderWriter'; + final String readerName = '${codecName}Reader'; + final String writerName = '${codecName}Writer'; + + final Iterable allTypes = getEnumeratedTypes(root); + // Generate Reader + indent.write('private class $readerName: FlutterStandardReader '); + indent.addScoped('{', '}', () { + if (allTypes.isNotEmpty) { + indent.write('override func readValue(ofType type: UInt8) -> Any? '); + indent.addScoped('{', '}', () { + indent.write('switch type '); + indent.addScoped('{', '}', nestCount: 0, () { + for (final EnumeratedType customType in allTypes) { + indent.writeln('case ${customType.enumeration}:'); + indent.nest(1, () { + if (customType.type == CustomTypes.customEnum) { + indent.writeln('var enumResult: ${customType.name}? = nil'); + indent.writeln( + 'let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int)'); + indent.writeScoped( + 'if let enumResultAsInt = enumResultAsInt {', '}', () { + indent.writeln( + 'enumResult = ${customType.name}(rawValue: enumResultAsInt)'); + }); + indent.writeln('return enumResult'); + } else { + indent.writeln( + 'return ${customType.name}.fromList(self.readValue() as! [Any?])'); + } + }); + } + indent.writeln('default:'); + indent.nest(1, () { + indent.writeln('return super.readValue(ofType: type)'); + }); + }); + }); + } + }); + + // Generate Writer + indent.newln(); + indent.write('private class $writerName: FlutterStandardWriter '); + indent.addScoped('{', '}', () { + if (allTypes.isNotEmpty) { + indent.write('override func writeValue(_ value: Any) '); + indent.addScoped('{', '}', () { + indent.write(''); + for (final EnumeratedType customType in allTypes) { + indent.add('if let value = value as? ${customType.name} '); + indent.addScoped('{', '} else ', () { + indent.writeln('super.writeByte(${customType.enumeration})'); + if (customType.type == CustomTypes.customEnum) { + indent.writeln('super.writeValue(value.rawValue)'); + } else if (customType.type == CustomTypes.customClass) { + indent.writeln('super.writeValue(value.toList())'); + } + }, addTrailingNewline: false); + } + indent.addScoped('{', '}', () { + indent.writeln('super.writeValue(value)'); + }); + }); + } + }); + indent.newln(); + + // Generate ReaderWriter + indent + .write('private class $readerWriterName: FlutterStandardReaderWriter '); + indent.addScoped('{', '}', () { + indent.write( + 'override func reader(with data: Data) -> FlutterStandardReader '); + indent.addScoped('{', '}', () { + indent.writeln('return $readerName(data: data)'); + }); + indent.newln(); + indent.write( + 'override func writer(with data: NSMutableData) -> FlutterStandardWriter '); + indent.addScoped('{', '}', () { + indent.writeln('return $writerName(data: data)'); + }); + }); + indent.newln(); + + // Generate Codec + indent.write( + 'class $codecName: FlutterStandardMessageCodec, @unchecked Sendable '); + indent.addScoped('{', '}', () { + indent.writeln( + 'static let shared = $codecName(readerWriter: $readerWriterName())'); + }); + indent.newln(); + } + @override void writeDataClass( SwiftOptions generatorOptions, @@ -210,18 +328,7 @@ class SwiftGenerator extends StructuredGenerator { final String separator = classDefinition.fields.length > 1 ? ',' : ''; for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) { - String toWriteValue = ''; - final String fieldName = field.name; - final String nullsafe = field.type.isNullable ? '?' : ''; - if (field.type.isClass) { - toWriteValue = '$fieldName$nullsafe.toList()'; - } else if (field.type.isEnum) { - toWriteValue = '$fieldName$nullsafe.rawValue'; - } else { - toWriteValue = field.name; - } - - indent.writeln('$toWriteValue$separator'); + indent.writeln('${field.name}$separator'); } }); }); @@ -236,17 +343,20 @@ class SwiftGenerator extends StructuredGenerator { required String dartPackageName, }) { final String className = classDefinition.name; - indent.write('static func fromList(_ list: [Any?]) -> $className? '); + indent.writeln('// swift-format-ignore: AlwaysUseLowerCamelCase'); + indent.write( + 'static func fromList(_ ${varNamePrefix}list: [Any?]) -> $className? '); indent.addScoped('{', '}', () { enumerate(getFieldsInSerializationOrder(classDefinition), (int index, final NamedType field) { - final String listValue = 'list[$index]'; + final String listValue = '${varNamePrefix}list[$index]'; - _writeDecodeCasting( + _writeGenericCasting( indent: indent, value: listValue, variableName: field.name, + fieldType: _swiftTypeForDartType(field.type), type: field.type, ); }); @@ -297,11 +407,6 @@ class SwiftGenerator extends StructuredGenerator { AstFlutterApi api, { required String dartPackageName, }) { - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; - if (isCustomCodec) { - _writeCodec(indent, api, root); - } - const List generatedComments = [ ' Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.' ]; @@ -316,7 +421,7 @@ class SwiftGenerator extends StructuredGenerator { name: func.name, parameters: func.parameters, returnType: func.returnType, - errorTypeName: 'FlutterError', + errorTypeName: _getErrorClassName(generatorOptions), isAsynchronous: true, swiftFunction: func.swiftFunction, getParameterName: _getSafeArgumentName, @@ -335,26 +440,22 @@ class SwiftGenerator extends StructuredGenerator { indent.writeln( r'self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""'); }); - final String codecName = _getCodecName(api); - String codecArgumentString = ''; - if (getCodecClasses(api, root).isNotEmpty) { - codecArgumentString = ', codec: codec'; - indent.write('var codec: FlutterStandardMessageCodec '); - indent.addScoped('{', '}', () { - indent.writeln('return $codecName.shared'); - }); - } + final String codecName = _getCodecName(generatorOptions); + indent.write('var codec: $codecName '); + indent.addScoped('{', '}', () { + indent.writeln('return $codecName.shared'); + }); for (final Method func in api.methods) { addDocumentationComments( indent, func.documentationComments, _docCommentSpec); _writeFlutterMethod( indent, + generatorOptions: generatorOptions, name: func.name, channelName: makeChannelName(api, func, dartPackageName), parameters: func.parameters, returnType: func.returnType, - codecArgumentString: codecArgumentString, swiftFunction: func.swiftFunction, ); } @@ -376,10 +477,6 @@ class SwiftGenerator extends StructuredGenerator { }) { final String apiName = api.name; - final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; - if (isCustomCodec) { - _writeCodec(indent, api, root); - } const List generatedComments = [ ' Generated protocol from Pigeon that represents a handler of messages from Flutter.' ]; @@ -407,14 +504,8 @@ class SwiftGenerator extends StructuredGenerator { '$_docCommentPrefix Generated setup class from Pigeon to handle messages through the `binaryMessenger`.'); indent.write('class ${apiName}Setup '); indent.addScoped('{', '}', () { - final String codecName = _getCodecName(api); - indent.writeln('$_docCommentPrefix The codec used by $apiName.'); - String codecArgumentString = ''; - if (getCodecClasses(api, root).isNotEmpty) { - codecArgumentString = ', codec: codec'; - indent.writeln( - 'static var codec: FlutterStandardMessageCodec { $codecName.shared }'); - } + indent.writeln( + 'static var codec: FlutterStandardMessageCodec { ${_getCodecName(generatorOptions)}.shared }'); indent.writeln( '$_docCommentPrefix Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`.'); indent.write( @@ -430,7 +521,6 @@ class SwiftGenerator extends StructuredGenerator { parameters: method.parameters, returnType: method.returnType, isAsynchronous: method.isAsynchronous, - codecArgumentString: codecArgumentString, swiftFunction: method.swiftFunction, documentationComments: method.documentationComments, ); @@ -439,104 +529,9 @@ class SwiftGenerator extends StructuredGenerator { }); } - /// Writes the codec class will be used for encoding messages for the [api]. - /// Example: - /// private class FooHostApiCodecReader: FlutterStandardReader {...} - /// private class FooHostApiCodecWriter: FlutterStandardWriter {...} - /// private class FooHostApiCodecReaderWriter: FlutterStandardReaderWriter {...} - void _writeCodec(Indent indent, Api api, Root root) { - assert(getCodecClasses(api, root).isNotEmpty); - final String codecName = _getCodecName(api); - final String readerWriterName = '${codecName}ReaderWriter'; - final String readerName = '${codecName}Reader'; - final String writerName = '${codecName}Writer'; - - // Generate Reader - indent.write('private class $readerName: FlutterStandardReader '); - indent.addScoped('{', '}', () { - if (getCodecClasses(api, root).isNotEmpty) { - indent.write('override func readValue(ofType type: UInt8) -> Any? '); - indent.addScoped('{', '}', () { - indent.write('switch type '); - indent.addScoped('{', '}', nestCount: 0, () { - for (final EnumeratedClass customClass - in getCodecClasses(api, root)) { - indent.writeln('case ${customClass.enumeration}:'); - indent.nest(1, () { - indent.writeln( - 'return ${customClass.name}.fromList(self.readValue() as! [Any?])'); - }); - } - indent.writeln('default:'); - indent.nest(1, () { - indent.writeln('return super.readValue(ofType: type)'); - }); - }); - }); - } - }); - - // Generate Writer - indent.newln(); - indent.write('private class $writerName: FlutterStandardWriter '); - indent.addScoped('{', '}', () { - if (getCodecClasses(api, root).isNotEmpty) { - indent.write('override func writeValue(_ value: Any) '); - indent.addScoped('{', '}', () { - indent.write(''); - for (final EnumeratedClass customClass - in getCodecClasses(api, root)) { - indent.add('if let value = value as? ${customClass.name} '); - indent.addScoped('{', '} else ', () { - indent.writeln('super.writeByte(${customClass.enumeration})'); - indent.writeln('super.writeValue(value.toList())'); - }, addTrailingNewline: false); - } - indent.addScoped('{', '}', () { - indent.writeln('super.writeValue(value)'); - }); - }); - } - }); - indent.newln(); - - // Generate ReaderWriter - indent - .write('private class $readerWriterName: FlutterStandardReaderWriter '); - indent.addScoped('{', '}', () { - indent.write( - 'override func reader(with data: Data) -> FlutterStandardReader '); - indent.addScoped('{', '}', () { - indent.writeln('return $readerName(data: data)'); - }); - indent.newln(); - indent.write( - 'override func writer(with data: NSMutableData) -> FlutterStandardWriter '); - indent.addScoped('{', '}', () { - indent.writeln('return $writerName(data: data)'); - }); - }); - indent.newln(); - - // Generate Codec - indent.write('class $codecName: FlutterStandardMessageCodec '); - indent.addScoped('{', '}', () { - indent.writeln( - 'static let shared = $codecName(readerWriter: $readerWriterName())'); - }); - indent.newln(); - } - String _castForceUnwrap(String value, TypeDeclaration type) { assert(!type.isVoid); - if (type.isEnum) { - String output = - '${_swiftTypeForDartType(type)}(rawValue: $value as! Int)!'; - if (type.isNullable) { - output = 'isNullish($value) ? nil : $output'; - } - return output; - } else if (type.baseName == 'Object') { + if (type.baseName == 'Object') { return value + (type.isNullable ? '' : '!'); } else if (type.baseName == 'int') { if (type.isNullable) { @@ -568,61 +563,6 @@ class SwiftGenerator extends StructuredGenerator { } } - /// Writes decode and casting code for any type. - /// - /// Optional parameters should only be used for class decoding. - void _writeDecodeCasting({ - required Indent indent, - required String value, - required String variableName, - required TypeDeclaration type, - }) { - final String fieldType = _swiftTypeForDartType(type); - - if (type.isNullable) { - if (type.isClass) { - indent.writeln('var $variableName: $fieldType? = nil'); - indent - .write('if let ${variableName}List: [Any?] = nilOrValue($value) '); - indent.addScoped('{', '}', () { - indent.writeln( - '$variableName = $fieldType.fromList(${variableName}List)'); - }); - } else if (type.isEnum) { - indent.writeln('var $variableName: $fieldType? = nil'); - indent.writeln( - 'let ${variableName}EnumVal: Int? = ${_castForceUnwrap(value, const TypeDeclaration(baseName: 'Int', isNullable: true))}'); - indent - .write('if let ${variableName}RawValue = ${variableName}EnumVal '); - indent.addScoped('{', '}', () { - indent.writeln( - '$variableName = $fieldType(rawValue: ${variableName}RawValue)!'); - }); - } else { - _writeGenericCasting( - indent: indent, - value: value, - variableName: variableName, - fieldType: fieldType, - type: type, - ); - } - } else { - if (type.isClass) { - indent.writeln( - 'let $variableName = $fieldType.fromList($value as! [Any?])!'); - } else { - _writeGenericCasting( - indent: indent, - value: value, - variableName: variableName, - fieldType: fieldType, - type: type, - ); - } - } - } - void _writeIsNullish(Indent indent) { indent.newln(); indent.write('private func isNullish(_ value: Any?) -> Bool '); @@ -639,10 +579,20 @@ class SwiftGenerator extends StructuredGenerator { }); } - void _writeWrapError(Indent indent) { + void _writeWrapError(SwiftOptions generatorOptions, Indent indent) { indent.newln(); indent.write('private func wrapError(_ error: Any) -> [Any?] '); indent.addScoped('{', '}', () { + indent.write( + 'if let pigeonError = error as? ${_getErrorClassName(generatorOptions)} '); + indent.addScoped('{', '}', () { + indent.write('return '); + indent.addScoped('[', ']', () { + indent.writeln('pigeonError.code,'); + indent.writeln('pigeonError.message,'); + indent.writeln('pigeonError.details,'); + }); + }); indent.write('if let flutterError = error as? FlutterError '); indent.addScoped('{', '}', () { indent.write('return '); @@ -670,13 +620,14 @@ private func nilOrValue(_ value: Any?) -> T? { }'''); } - void _writeCreateConnectionError(Indent indent) { + void _writeCreateConnectionError( + SwiftOptions generatorOptions, Indent indent) { indent.newln(); indent.writeScoped( - 'private func createConnectionError(withChannelName channelName: String) -> FlutterError {', + 'private func createConnectionError(withChannelName channelName: String) -> ${_getErrorClassName(generatorOptions)} {', '}', () { indent.writeln( - 'return FlutterError(code: "channel-error", message: "Unable to establish connection on channel: \'\\(channelName)\'.", details: "")'); + 'return ${_getErrorClassName(generatorOptions)}(code: "channel-error", message: "Unable to establish connection on channel: \'\\(channelName)\'.", details: "")'); }); } @@ -694,31 +645,34 @@ private func nilOrValue(_ value: Any?) -> T? { .whereType() .any((Api api) => api.methods.isNotEmpty); + _writePigeonError(generatorOptions, indent); + if (hasHostApi) { _writeWrapResult(indent); - _writeWrapError(indent); + _writeWrapError(generatorOptions, indent); } if (hasFlutterApi) { - _writeCreateConnectionError(indent); + _writeCreateConnectionError(generatorOptions, indent); } + _writeIsNullish(indent); _writeNilOrValue(indent); } void _writeFlutterMethod( Indent indent, { + required SwiftOptions generatorOptions, required String name, required String channelName, required List parameters, required TypeDeclaration returnType, - required String codecArgumentString, required String? swiftFunction, }) { final String methodSignature = _getMethodSignature( name: name, parameters: parameters, returnType: returnType, - errorTypeName: 'FlutterError', + errorTypeName: _getErrorClassName(generatorOptions), isAsynchronous: true, swiftFunction: swiftFunction, getParameterName: _getSafeArgumentName, @@ -726,12 +680,7 @@ private func nilOrValue(_ value: Any?) -> T? { /// Returns an argument name that can be used in a context where it is possible to collide. String getEnumSafeArgumentExpression(int count, NamedType argument) { - String enumTag = ''; - if (argument.type.isEnum) { - enumTag = argument.type.isNullable ? '?.rawValue' : '.rawValue'; - } - - return '${_getArgumentName(count, argument)}Arg$enumTag'; + return '${_getArgumentName(count, argument)}Arg'; } indent.writeScoped('$methodSignature {', '}', () { @@ -745,7 +694,7 @@ private func nilOrValue(_ value: Any?) -> T? { indent.writeln( 'let channelName: String = "$channelName\\(messageChannelSuffix)"'); indent.writeln( - 'let $channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger$codecArgumentString)'); + 'let $channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)'); indent.write('$channel.sendMessage($sendArgument) '); indent.addScoped('{ response in', '}', () { @@ -760,12 +709,12 @@ private func nilOrValue(_ value: Any?) -> T? { indent.writeln('let message: String? = nilOrValue(listResponse[1])'); indent.writeln('let details: String? = nilOrValue(listResponse[2])'); indent.writeln( - 'completion(.failure(FlutterError(code: code, message: message, details: details)))'); + 'completion(.failure(${_getErrorClassName(generatorOptions)}(code: code, message: message, details: details)))'); }, addTrailingNewline: false); if (!returnType.isNullable && !returnType.isVoid) { indent.addScoped('else if listResponse[0] == nil {', '} ', () { indent.writeln( - 'completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))'); + 'completion(.failure(${_getErrorClassName(generatorOptions)}(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))'); }, addTrailingNewline: false); } indent.addScoped('else {', '}', () { @@ -794,7 +743,6 @@ private func nilOrValue(_ value: Any?) -> T? { required Iterable parameters, required TypeDeclaration returnType, required bool isAsynchronous, - required String codecArgumentString, required String? swiftFunction, List documentationComments = const [], }) { @@ -808,7 +756,7 @@ private func nilOrValue(_ value: Any?) -> T? { final String varChannelName = '${name}Channel'; addDocumentationComments(indent, documentationComments, _docCommentSpec); indent.writeln( - 'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName\\(channelSuffix)", binaryMessenger: binaryMessenger$codecArgumentString)'); + 'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName\\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)'); indent.write('if let api = api '); indent.addScoped('{', '}', () { indent.write('$varChannelName.setMessageHandler '); @@ -854,12 +802,9 @@ private func nilOrValue(_ value: Any?) -> T? { indent.addScoped('{ result in', '}', () { indent.write('switch result '); indent.addScoped('{', '}', nestCount: 0, () { - final String nullsafe = returnType.isNullable ? '?' : ''; - final String enumTag = - returnType.isEnum ? '$nullsafe.rawValue' : ''; indent.writeln('case .success$successVariableInit:'); indent.nest(1, () { - indent.writeln('reply(wrapResult($resultName$enumTag))'); + indent.writeln('reply(wrapResult($resultName))'); }); indent.writeln('case .failure(let error):'); indent.nest(1, () { @@ -874,15 +819,8 @@ private func nilOrValue(_ value: Any?) -> T? { indent.writeln(call); indent.writeln('reply(wrapResult(nil))'); } else { - String enumTag = ''; - if (returnType.isEnum) { - enumTag = '.rawValue'; - } - enumTag = returnType.isNullable && returnType.isEnum - ? '?$enumTag' - : enumTag; indent.writeln('let result = $call'); - indent.writeln('reply(wrapResult(result$enumTag))'); + indent.writeln('reply(wrapResult(result))'); } }, addTrailingNewline: false); indent.addScoped(' catch {', '}', () { @@ -895,17 +833,52 @@ private func nilOrValue(_ value: Any?) -> T? { indent.writeln('$varChannelName.setMessageHandler(nil)'); }); } + + void _writePigeonError(SwiftOptions generatorOptions, Indent indent) { + indent.newln(); + indent.writeln( + '/// Error class for passing custom error details to Dart side.'); + indent.writeScoped( + 'final class ${_getErrorClassName(generatorOptions)}: Error {', '}', + () { + indent.writeln('let code: String'); + indent.writeln('let message: String?'); + indent.writeln('let details: Any?'); + indent.newln(); + indent.writeScoped( + 'init(code: String, message: String?, details: Any?) {', '}', () { + indent.writeln('self.code = code'); + indent.writeln('self.message = message'); + indent.writeln('self.details = details'); + }); + indent.newln(); + indent.writeScoped('var localizedDescription: String {', '}', () { + indent.writeScoped('return', '', () { + indent.writeln( + '"${_getErrorClassName(generatorOptions)}(code: \\(code), message: \\(message ?? ""), details: \\(details ?? "")"'); + }, addTrailingNewline: false); + }); + }); + } } /// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(Api api) => '${api.name}Codec'; +String _getCodecName(SwiftOptions options) { + return '${options.fileSpecificClassNameComponent}PigeonCodec'; +} + +String _getErrorClassName(SwiftOptions generatorOptions) { + return generatorOptions.errorClassName ?? 'PigeonError'; +} -String _getArgumentName(int count, NamedType argument) => - argument.name.isEmpty ? 'arg$count' : argument.name; +String _getArgumentName(int count, NamedType argument) { + return argument.name.isEmpty ? 'arg$count' : argument.name; +} /// Returns an argument name that can be used in a context where it is possible to collide. -String _getSafeArgumentName(int count, NamedType argument) => - '${_getArgumentName(count, argument)}Arg'; +String _getSafeArgumentName(int count, NamedType argument) { + return '${_getArgumentName(count, argument)}Arg'; +} String _camelCase(String text) { final String pascal = text.split('_').map((String part) { @@ -921,7 +894,8 @@ String _flattenTypeArguments(List args) { } String _swiftTypeForBuiltinGenericDartType(TypeDeclaration type) { - if (type.typeArguments.isEmpty) { + if (type.typeArguments.isEmpty || + (type.typeArguments.first.baseName == 'Object')) { if (type.baseName == 'List') { return '[Any?]'; } else if (type.baseName == 'Map') { diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart index 3107bc55353..b4dc3fdfb02 100644 --- a/packages/pigeon/pigeons/core_tests.dart +++ b/packages/pigeon/pigeons/core_tests.dart @@ -23,11 +23,21 @@ class AllTypes { required this.a4ByteArray, required this.a8ByteArray, required this.aFloatArray, - this.aList = const [], - this.aMap = const {}, this.anEnum = AnEnum.one, this.aString = '', this.anObject = 0, + + // Lists + // This name is in a different format than the others to ensure that name + // collision with the work 'list' doesn't occur in the generated files. + required this.list, + required this.stringList, + required this.intList, + required this.doubleList, + required this.boolList, + + // Maps + required this.map, }); bool aBool; @@ -38,13 +48,21 @@ class AllTypes { Int32List a4ByteArray; Int64List a8ByteArray; Float64List aFloatArray; - // ignore: always_specify_types, strict_raw_type - List aList; - // ignore: always_specify_types, strict_raw_type - Map aMap; AnEnum anEnum; String aString; Object anObject; + + // Lists + // ignore: strict_raw_type, always_specify_types + List list; + List stringList; + List intList; + List doubleList; + List boolList; + + // Maps + // ignore: strict_raw_type, always_specify_types + Map map; } /// A class containing all supported nullable types. @@ -59,8 +77,6 @@ class AllNullableTypes { this.aNullable4ByteArray, this.aNullable8ByteArray, this.aNullableFloatArray, - this.aNullableList, - this.aNullableMap, this.nullableNestedList, this.nullableMapWithAnnotations, this.nullableMapWithObject, @@ -68,6 +84,17 @@ class AllNullableTypes { this.aNullableString, this.aNullableObject, this.allNullableTypes, + + // Lists + this.list, + this.stringList, + this.intList, + this.doubleList, + this.boolList, + this.nestedClassList, + + //Maps + this.map, ); bool? aNullableBool; @@ -78,10 +105,6 @@ class AllNullableTypes { Int32List? aNullable4ByteArray; Int64List? aNullable8ByteArray; Float64List? aNullableFloatArray; - // ignore: always_specify_types, strict_raw_type - List? aNullableList; - // ignore: always_specify_types, strict_raw_type - Map? aNullableMap; List?>? nullableNestedList; Map? nullableMapWithAnnotations; Map? nullableMapWithObject; @@ -89,6 +112,19 @@ class AllNullableTypes { String? aNullableString; Object? aNullableObject; AllNullableTypes? allNullableTypes; + + // Lists + // ignore: strict_raw_type, always_specify_types + List? list; + List? stringList; + List? intList; + List? doubleList; + List? boolList; + List? nestedClassList; + + // Maps + // ignore: strict_raw_type, always_specify_types + Map? map; } /// The primary purpose for this class is to ensure coverage of Swift structs @@ -104,14 +140,22 @@ class AllNullableTypesWithoutRecursion { this.aNullable4ByteArray, this.aNullable8ByteArray, this.aNullableFloatArray, - this.aNullableList, - this.aNullableMap, this.nullableNestedList, this.nullableMapWithAnnotations, this.nullableMapWithObject, this.aNullableEnum, this.aNullableString, this.aNullableObject, + + // Lists + this.list, + this.stringList, + this.intList, + this.doubleList, + this.boolList, + + //Maps + this.map, ); bool? aNullableBool; @@ -122,16 +166,24 @@ class AllNullableTypesWithoutRecursion { Int32List? aNullable4ByteArray; Int64List? aNullable8ByteArray; Float64List? aNullableFloatArray; - // ignore: always_specify_types, strict_raw_type - List? aNullableList; - // ignore: always_specify_types, strict_raw_type - Map? aNullableMap; List?>? nullableNestedList; Map? nullableMapWithAnnotations; Map? nullableMapWithObject; AnEnum? aNullableEnum; String? aNullableString; Object? aNullableObject; + + // Lists + // ignore: strict_raw_type, always_specify_types + List? list; + List? stringList; + List? intList; + List? doubleList; + List? boolList; + + // Maps + // ignore: strict_raw_type, always_specify_types + Map? map; } /// A class for testing nested class handling. @@ -204,7 +256,7 @@ abstract class HostIntegrationCoreApi { /// Returns the passed list, to test serialization and deserialization. @ObjCSelector('echoList:') @SwiftFunction('echo(_:)') - List echoList(List aList); + List echoList(List list); /// Returns the passed map, to test serialization and deserialization. @ObjCSelector('echoMap:') @@ -375,7 +427,7 @@ abstract class HostIntegrationCoreApi { @async @ObjCSelector('echoAsyncList:') @SwiftFunction('echoAsync(_:)') - List echoAsyncList(List aList); + List echoAsyncList(List list); /// Returns the passed map, to test asynchronous serialization and deserialization. @async @@ -462,7 +514,7 @@ abstract class HostIntegrationCoreApi { @async @ObjCSelector('echoAsyncNullableList:') @SwiftFunction('echoAsyncNullable(_:)') - List? echoAsyncNullableList(List? aList); + List? echoAsyncNullableList(List? list); /// Returns the passed map, to test asynchronous serialization and deserialization. @async @@ -543,12 +595,12 @@ abstract class HostIntegrationCoreApi { @async @ObjCSelector('callFlutterEchoUint8List:') @SwiftFunction('callFlutterEcho(_:)') - Uint8List callFlutterEchoUint8List(Uint8List aList); + Uint8List callFlutterEchoUint8List(Uint8List list); @async @ObjCSelector('callFlutterEchoList:') @SwiftFunction('callFlutterEcho(_:)') - List callFlutterEchoList(List aList); + List callFlutterEchoList(List list); @async @ObjCSelector('callFlutterEchoMap:') @@ -583,12 +635,12 @@ abstract class HostIntegrationCoreApi { @async @ObjCSelector('callFlutterEchoNullableUint8List:') @SwiftFunction('callFlutterEchoNullable(_:)') - Uint8List? callFlutterEchoNullableUint8List(Uint8List? aList); + Uint8List? callFlutterEchoNullableUint8List(Uint8List? list); @async @ObjCSelector('callFlutterEchoNullableList:') @SwiftFunction('callFlutterEchoNullable(_:)') - List? callFlutterEchoNullableList(List? aList); + List? callFlutterEchoNullableList(List? list); @async @ObjCSelector('callFlutterEchoNullableMap:') @@ -679,12 +731,12 @@ abstract class FlutterIntegrationCoreApi { /// Returns the passed byte list, to test serialization and deserialization. @ObjCSelector('echoUint8List:') @SwiftFunction('echo(_:)') - Uint8List echoUint8List(Uint8List aList); + Uint8List echoUint8List(Uint8List list); /// Returns the passed list, to test serialization and deserialization. @ObjCSelector('echoList:') @SwiftFunction('echo(_:)') - List echoList(List aList); + List echoList(List list); /// Returns the passed map, to test serialization and deserialization. @ObjCSelector('echoMap:') @@ -721,12 +773,12 @@ abstract class FlutterIntegrationCoreApi { /// Returns the passed byte list, to test serialization and deserialization. @ObjCSelector('echoNullableUint8List:') @SwiftFunction('echoNullable(_:)') - Uint8List? echoNullableUint8List(Uint8List? aList); + Uint8List? echoNullableUint8List(Uint8List? list); /// Returns the passed list, to test serialization and deserialization. @ObjCSelector('echoNullableList:') @SwiftFunction('echoNullable(_:)') - List? echoNullableList(List? aList); + List? echoNullableList(List? list); /// Returns the passed map, to test serialization and deserialization. @ObjCSelector('echoNullableMap:') diff --git a/packages/pigeon/platform_tests/README.md b/packages/pigeon/platform_tests/README.md index b67437fd831..88578bffc24 100644 --- a/packages/pigeon/platform_tests/README.md +++ b/packages/pigeon/platform_tests/README.md @@ -15,7 +15,7 @@ necessary Pigeon output. The new unified test harness for all platforms. Tests in this plugin use the same structure as tests for the Flutter team-maintained plugins, as described -[in the repository documentation](https://github.com/flutter/flutter/wiki/Plugin-Tests). +[in the repository documentation](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests). ## alternate\_language\_test\_plugin diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java index 04f60792293..ef8c5787338 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java @@ -109,8 +109,8 @@ public void throwErrorFromVoid() { } @Override - public @NonNull List echoList(@NonNull List aList) { - return aList; + public @NonNull List echoList(@NonNull List list) { + return list; } @Override @@ -160,13 +160,11 @@ public void throwErrorFromVoid() { @Nullable Boolean aNullableBool, @Nullable Long aNullableInt, @Nullable String aNullableString) { - AllNullableTypes someThings = - new AllNullableTypes.Builder() - .setANullableBool(aNullableBool) - .setANullableInt(aNullableInt) - .setANullableString(aNullableString) - .build(); - return someThings; + return new AllNullableTypes.Builder() + .setANullableBool(aNullableBool) + .setANullableInt(aNullableInt) + .setANullableString(aNullableString) + .build(); } @Override @@ -174,13 +172,11 @@ public void throwErrorFromVoid() { @Nullable Boolean aNullableBool, @Nullable Long aNullableInt, @Nullable String aNullableString) { - AllNullableTypesWithoutRecursion someThings = - new AllNullableTypesWithoutRecursion.Builder() - .setANullableBool(aNullableBool) - .setANullableInt(aNullableInt) - .setANullableString(aNullableString) - .build(); - return someThings; + return new AllNullableTypesWithoutRecursion.Builder() + .setANullableBool(aNullableBool) + .setANullableInt(aNullableInt) + .setANullableString(aNullableString) + .build(); } @Override @@ -307,8 +303,8 @@ public void echoAsyncObject(@NonNull Object anObject, @NonNull Result re } @Override - public void echoAsyncList(@NonNull List aList, @NonNull Result> result) { - result.success(aList); + public void echoAsyncList(@NonNull List list, @NonNull Result> result) { + result.success(list); } @Override @@ -359,8 +355,8 @@ public void echoAsyncNullableObject( @Override public void echoAsyncNullableList( - @Nullable List aList, @NonNull NullableResult> result) { - result.success(aList); + @Nullable List list, @NonNull NullableResult> result) { + result.success(list); } @Override @@ -377,28 +373,33 @@ public void echoAsyncNullableEnum( @Override public void callFlutterNoop(@NonNull VoidResult result) { + assert flutterApi != null; flutterApi.noop(result); } @Override public void callFlutterThrowError(@NonNull NullableResult result) { + assert flutterApi != null; flutterApi.throwError(result); } @Override public void callFlutterThrowErrorFromVoid(@NonNull VoidResult result) { + assert flutterApi != null; flutterApi.throwErrorFromVoid(result); } @Override public void callFlutterEchoAllTypes( @NonNull AllTypes everything, @NonNull Result result) { + assert flutterApi != null; flutterApi.echoAllTypes(everything, result); } @Override public void callFlutterEchoAllNullableTypes( @Nullable AllNullableTypes everything, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoAllNullableTypes(everything, result); } @@ -408,6 +409,7 @@ public void callFlutterSendMultipleNullableTypes( @Nullable Long aNullableInt, @Nullable String aNullableString, @NonNull Result result) { + assert flutterApi != null; flutterApi.sendMultipleNullableTypes(aNullableBool, aNullableInt, aNullableString, result); } @@ -415,6 +417,7 @@ public void callFlutterSendMultipleNullableTypes( public void callFlutterEchoAllNullableTypesWithoutRecursion( @Nullable AllNullableTypesWithoutRecursion everything, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoAllNullableTypesWithoutRecursion(everything, result); } @@ -424,97 +427,114 @@ public void callFlutterSendMultipleNullableTypesWithoutRecursion( @Nullable Long aNullableInt, @Nullable String aNullableString, @NonNull Result result) { + assert flutterApi != null; flutterApi.sendMultipleNullableTypesWithoutRecursion( aNullableBool, aNullableInt, aNullableString, result); } @Override public void callFlutterEchoBool(@NonNull Boolean aBool, @NonNull Result result) { + assert flutterApi != null; flutterApi.echoBool(aBool, result); } @Override public void callFlutterEchoInt(@NonNull Long anInt, @NonNull Result result) { + assert flutterApi != null; flutterApi.echoInt(anInt, result); } @Override public void callFlutterEchoDouble(@NonNull Double aDouble, @NonNull Result result) { + assert flutterApi != null; flutterApi.echoDouble(aDouble, result); } @Override public void callFlutterEchoString(@NonNull String aString, @NonNull Result result) { + assert flutterApi != null; flutterApi.echoString(aString, result); } @Override - public void callFlutterEchoUint8List(@NonNull byte[] aList, @NonNull Result result) { - flutterApi.echoUint8List(aList, result); + public void callFlutterEchoUint8List(@NonNull byte[] list, @NonNull Result result) { + assert flutterApi != null; + flutterApi.echoUint8List(list, result); } @Override public void callFlutterEchoList( - @NonNull List aList, @NonNull Result> result) { - flutterApi.echoList(aList, result); + @NonNull List list, @NonNull Result> result) { + assert flutterApi != null; + flutterApi.echoList(list, result); } @Override public void callFlutterEchoMap( @NonNull Map aMap, @NonNull Result> result) { + assert flutterApi != null; flutterApi.echoMap(aMap, result); } @Override public void callFlutterEchoEnum(@NonNull AnEnum anEnum, @NonNull Result result) { + assert flutterApi != null; flutterApi.echoEnum(anEnum, result); } @Override public void callFlutterEchoNullableBool( @Nullable Boolean aBool, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoNullableBool(aBool, result); } @Override public void callFlutterEchoNullableInt( @Nullable Long anInt, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoNullableInt(anInt, result); } @Override public void callFlutterEchoNullableDouble( @Nullable Double aDouble, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoNullableDouble(aDouble, result); } @Override public void callFlutterEchoNullableString( @Nullable String aString, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoNullableString(aString, result); } @Override public void callFlutterEchoNullableUint8List( - @Nullable byte[] aList, @NonNull NullableResult result) { - flutterApi.echoNullableUint8List(aList, result); + @Nullable byte[] list, @NonNull NullableResult result) { + assert flutterApi != null; + flutterApi.echoNullableUint8List(list, result); } @Override public void callFlutterEchoNullableList( - @Nullable List aList, @NonNull NullableResult> result) { - flutterApi.echoNullableList(aList, result); + @Nullable List list, @NonNull NullableResult> result) { + assert flutterApi != null; + flutterApi.echoNullableList(list, result); } @Override public void callFlutterEchoNullableMap( @Nullable Map aMap, @NonNull NullableResult> result) { + assert flutterApi != null; flutterApi.echoNullableMap(aMap, result); } @Override public void callFlutterEchoNullableEnum( @Nullable AnEnum anEnum, @NonNull NullableResult result) { + assert flutterApi != null; flutterApi.echoNullableEnum(anEnum, result); } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java index 1d5bde23a34..d7c5ea64ff1 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) @@ -198,32 +199,6 @@ public void setAFloatArray(@NonNull double[] setterArg) { this.aFloatArray = setterArg; } - private @NonNull List aList; - - public @NonNull List getAList() { - return aList; - } - - public void setAList(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"aList\" is null."); - } - this.aList = setterArg; - } - - private @NonNull Map aMap; - - public @NonNull Map getAMap() { - return aMap; - } - - public void setAMap(@NonNull Map setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"aMap\" is null."); - } - this.aMap = setterArg; - } - private @NonNull AnEnum anEnum; public @NonNull AnEnum getAnEnum() { @@ -263,9 +238,139 @@ public void setAnObject(@NonNull Object setterArg) { this.anObject = setterArg; } + private @NonNull List list; + + public @NonNull List getList() { + return list; + } + + public void setList(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"list\" is null."); + } + this.list = setterArg; + } + + private @NonNull List stringList; + + public @NonNull List getStringList() { + return stringList; + } + + public void setStringList(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"stringList\" is null."); + } + this.stringList = setterArg; + } + + private @NonNull List intList; + + public @NonNull List getIntList() { + return intList; + } + + public void setIntList(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"intList\" is null."); + } + this.intList = setterArg; + } + + private @NonNull List doubleList; + + public @NonNull List getDoubleList() { + return doubleList; + } + + public void setDoubleList(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"doubleList\" is null."); + } + this.doubleList = setterArg; + } + + private @NonNull List boolList; + + public @NonNull List getBoolList() { + return boolList; + } + + public void setBoolList(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"boolList\" is null."); + } + this.boolList = setterArg; + } + + private @NonNull Map map; + + public @NonNull Map getMap() { + return map; + } + + public void setMap(@NonNull Map setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"map\" is null."); + } + this.map = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ AllTypes() {} + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AllTypes that = (AllTypes) o; + return aBool.equals(that.aBool) + && anInt.equals(that.anInt) + && anInt64.equals(that.anInt64) + && aDouble.equals(that.aDouble) + && Arrays.equals(aByteArray, that.aByteArray) + && Arrays.equals(a4ByteArray, that.a4ByteArray) + && Arrays.equals(a8ByteArray, that.a8ByteArray) + && Arrays.equals(aFloatArray, that.aFloatArray) + && anEnum.equals(that.anEnum) + && aString.equals(that.aString) + && anObject.equals(that.anObject) + && list.equals(that.list) + && stringList.equals(that.stringList) + && intList.equals(that.intList) + && doubleList.equals(that.doubleList) + && boolList.equals(that.boolList) + && map.equals(that.map); + } + + @Override + public int hashCode() { + int __pigeon_result = + Objects.hash( + aBool, + anInt, + anInt64, + aDouble, + anEnum, + aString, + anObject, + list, + stringList, + intList, + doubleList, + boolList, + map); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(a4ByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(a8ByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aFloatArray); + return __pigeon_result; + } + public static final class Builder { private @Nullable Boolean aBool; @@ -332,43 +437,75 @@ public static final class Builder { return this; } - private @Nullable List aList; + private @Nullable AnEnum anEnum; @CanIgnoreReturnValue - public @NonNull Builder setAList(@NonNull List setterArg) { - this.aList = setterArg; + public @NonNull Builder setAnEnum(@NonNull AnEnum setterArg) { + this.anEnum = setterArg; return this; } - private @Nullable Map aMap; + private @Nullable String aString; @CanIgnoreReturnValue - public @NonNull Builder setAMap(@NonNull Map setterArg) { - this.aMap = setterArg; + public @NonNull Builder setAString(@NonNull String setterArg) { + this.aString = setterArg; return this; } - private @Nullable AnEnum anEnum; + private @Nullable Object anObject; @CanIgnoreReturnValue - public @NonNull Builder setAnEnum(@NonNull AnEnum setterArg) { - this.anEnum = setterArg; + public @NonNull Builder setAnObject(@NonNull Object setterArg) { + this.anObject = setterArg; return this; } - private @Nullable String aString; + private @Nullable List list; @CanIgnoreReturnValue - public @NonNull Builder setAString(@NonNull String setterArg) { - this.aString = setterArg; + public @NonNull Builder setList(@NonNull List setterArg) { + this.list = setterArg; return this; } - private @Nullable Object anObject; + private @Nullable List stringList; @CanIgnoreReturnValue - public @NonNull Builder setAnObject(@NonNull Object setterArg) { - this.anObject = setterArg; + public @NonNull Builder setStringList(@NonNull List setterArg) { + this.stringList = setterArg; + return this; + } + + private @Nullable List intList; + + @CanIgnoreReturnValue + public @NonNull Builder setIntList(@NonNull List setterArg) { + this.intList = setterArg; + return this; + } + + private @Nullable List doubleList; + + @CanIgnoreReturnValue + public @NonNull Builder setDoubleList(@NonNull List setterArg) { + this.doubleList = setterArg; + return this; + } + + private @Nullable List boolList; + + @CanIgnoreReturnValue + public @NonNull Builder setBoolList(@NonNull List setterArg) { + this.boolList = setterArg; + return this; + } + + private @Nullable Map map; + + @CanIgnoreReturnValue + public @NonNull Builder setMap(@NonNull Map setterArg) { + this.map = setterArg; return this; } @@ -382,18 +519,22 @@ public static final class Builder { pigeonReturn.setA4ByteArray(a4ByteArray); pigeonReturn.setA8ByteArray(a8ByteArray); pigeonReturn.setAFloatArray(aFloatArray); - pigeonReturn.setAList(aList); - pigeonReturn.setAMap(aMap); pigeonReturn.setAnEnum(anEnum); pigeonReturn.setAString(aString); pigeonReturn.setAnObject(anObject); + pigeonReturn.setList(list); + pigeonReturn.setStringList(stringList); + pigeonReturn.setIntList(intList); + pigeonReturn.setDoubleList(doubleList); + pigeonReturn.setBoolList(boolList); + pigeonReturn.setMap(map); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(13); + ArrayList toListResult = new ArrayList(17); toListResult.add(aBool); toListResult.add(anInt); toListResult.add(anInt64); @@ -402,46 +543,58 @@ ArrayList toList() { toListResult.add(a4ByteArray); toListResult.add(a8ByteArray); toListResult.add(aFloatArray); - toListResult.add(aList); - toListResult.add(aMap); - toListResult.add(anEnum == null ? null : anEnum.index); + toListResult.add(anEnum); toListResult.add(aString); toListResult.add(anObject); + toListResult.add(list); + toListResult.add(stringList); + toListResult.add(intList); + toListResult.add(doubleList); + toListResult.add(boolList); + toListResult.add(map); return toListResult; } - static @NonNull AllTypes fromList(@NonNull ArrayList list) { + static @NonNull AllTypes fromList(@NonNull ArrayList __pigeon_list) { AllTypes pigeonResult = new AllTypes(); - Object aBool = list.get(0); + Object aBool = __pigeon_list.get(0); pigeonResult.setABool((Boolean) aBool); - Object anInt = list.get(1); + Object anInt = __pigeon_list.get(1); pigeonResult.setAnInt( (anInt == null) ? null : ((anInt instanceof Integer) ? (Integer) anInt : (Long) anInt)); - Object anInt64 = list.get(2); + Object anInt64 = __pigeon_list.get(2); pigeonResult.setAnInt64( (anInt64 == null) ? null : ((anInt64 instanceof Integer) ? (Integer) anInt64 : (Long) anInt64)); - Object aDouble = list.get(3); + Object aDouble = __pigeon_list.get(3); pigeonResult.setADouble((Double) aDouble); - Object aByteArray = list.get(4); + Object aByteArray = __pigeon_list.get(4); pigeonResult.setAByteArray((byte[]) aByteArray); - Object a4ByteArray = list.get(5); + Object a4ByteArray = __pigeon_list.get(5); pigeonResult.setA4ByteArray((int[]) a4ByteArray); - Object a8ByteArray = list.get(6); + Object a8ByteArray = __pigeon_list.get(6); pigeonResult.setA8ByteArray((long[]) a8ByteArray); - Object aFloatArray = list.get(7); + Object aFloatArray = __pigeon_list.get(7); pigeonResult.setAFloatArray((double[]) aFloatArray); - Object aList = list.get(8); - pigeonResult.setAList((List) aList); - Object aMap = list.get(9); - pigeonResult.setAMap((Map) aMap); - Object anEnum = list.get(10); - pigeonResult.setAnEnum(AnEnum.values()[(int) anEnum]); - Object aString = list.get(11); + Object anEnum = __pigeon_list.get(8); + pigeonResult.setAnEnum((AnEnum) anEnum); + Object aString = __pigeon_list.get(9); pigeonResult.setAString((String) aString); - Object anObject = list.get(12); + Object anObject = __pigeon_list.get(10); pigeonResult.setAnObject(anObject); + Object list = __pigeon_list.get(11); + pigeonResult.setList((List) list); + Object stringList = __pigeon_list.get(12); + pigeonResult.setStringList((List) stringList); + Object intList = __pigeon_list.get(13); + pigeonResult.setIntList((List) intList); + Object doubleList = __pigeon_list.get(14); + pigeonResult.setDoubleList((List) doubleList); + Object boolList = __pigeon_list.get(15); + pigeonResult.setBoolList((List) boolList); + Object map = __pigeon_list.get(16); + pigeonResult.setMap((Map) map); return pigeonResult; } } @@ -532,26 +685,6 @@ public void setANullableFloatArray(@Nullable double[] setterArg) { this.aNullableFloatArray = setterArg; } - private @Nullable List aNullableList; - - public @Nullable List getANullableList() { - return aNullableList; - } - - public void setANullableList(@Nullable List setterArg) { - this.aNullableList = setterArg; - } - - private @Nullable Map aNullableMap; - - public @Nullable Map getANullableMap() { - return aNullableMap; - } - - public void setANullableMap(@Nullable Map setterArg) { - this.aNullableMap = setterArg; - } - private @Nullable List> nullableNestedList; public @Nullable List> getNullableNestedList() { @@ -622,6 +755,138 @@ public void setAllNullableTypes(@Nullable AllNullableTypes setterArg) { this.allNullableTypes = setterArg; } + private @Nullable List list; + + public @Nullable List getList() { + return list; + } + + public void setList(@Nullable List setterArg) { + this.list = setterArg; + } + + private @Nullable List stringList; + + public @Nullable List getStringList() { + return stringList; + } + + public void setStringList(@Nullable List setterArg) { + this.stringList = setterArg; + } + + private @Nullable List intList; + + public @Nullable List getIntList() { + return intList; + } + + public void setIntList(@Nullable List setterArg) { + this.intList = setterArg; + } + + private @Nullable List doubleList; + + public @Nullable List getDoubleList() { + return doubleList; + } + + public void setDoubleList(@Nullable List setterArg) { + this.doubleList = setterArg; + } + + private @Nullable List boolList; + + public @Nullable List getBoolList() { + return boolList; + } + + public void setBoolList(@Nullable List setterArg) { + this.boolList = setterArg; + } + + private @Nullable List nestedClassList; + + public @Nullable List getNestedClassList() { + return nestedClassList; + } + + public void setNestedClassList(@Nullable List setterArg) { + this.nestedClassList = setterArg; + } + + private @Nullable Map map; + + public @Nullable Map getMap() { + return map; + } + + public void setMap(@Nullable Map setterArg) { + this.map = setterArg; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AllNullableTypes that = (AllNullableTypes) o; + return Objects.equals(aNullableBool, that.aNullableBool) + && Objects.equals(aNullableInt, that.aNullableInt) + && Objects.equals(aNullableInt64, that.aNullableInt64) + && Objects.equals(aNullableDouble, that.aNullableDouble) + && Arrays.equals(aNullableByteArray, that.aNullableByteArray) + && Arrays.equals(aNullable4ByteArray, that.aNullable4ByteArray) + && Arrays.equals(aNullable8ByteArray, that.aNullable8ByteArray) + && Arrays.equals(aNullableFloatArray, that.aNullableFloatArray) + && Objects.equals(nullableNestedList, that.nullableNestedList) + && Objects.equals(nullableMapWithAnnotations, that.nullableMapWithAnnotations) + && Objects.equals(nullableMapWithObject, that.nullableMapWithObject) + && Objects.equals(aNullableEnum, that.aNullableEnum) + && Objects.equals(aNullableString, that.aNullableString) + && Objects.equals(aNullableObject, that.aNullableObject) + && Objects.equals(allNullableTypes, that.allNullableTypes) + && Objects.equals(list, that.list) + && Objects.equals(stringList, that.stringList) + && Objects.equals(intList, that.intList) + && Objects.equals(doubleList, that.doubleList) + && Objects.equals(boolList, that.boolList) + && Objects.equals(nestedClassList, that.nestedClassList) + && Objects.equals(map, that.map); + } + + @Override + public int hashCode() { + int __pigeon_result = + Objects.hash( + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + nestedClassList, + map); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable4ByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable8ByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableFloatArray); + return __pigeon_result; + } + public static final class Builder { private @Nullable Boolean aNullableBool; @@ -688,22 +953,6 @@ public static final class Builder { return this; } - private @Nullable List aNullableList; - - @CanIgnoreReturnValue - public @NonNull Builder setANullableList(@Nullable List setterArg) { - this.aNullableList = setterArg; - return this; - } - - private @Nullable Map aNullableMap; - - @CanIgnoreReturnValue - public @NonNull Builder setANullableMap(@Nullable Map setterArg) { - this.aNullableMap = setterArg; - return this; - } - private @Nullable List> nullableNestedList; @CanIgnoreReturnValue @@ -761,6 +1010,62 @@ public static final class Builder { return this; } + private @Nullable List list; + + @CanIgnoreReturnValue + public @NonNull Builder setList(@Nullable List setterArg) { + this.list = setterArg; + return this; + } + + private @Nullable List stringList; + + @CanIgnoreReturnValue + public @NonNull Builder setStringList(@Nullable List setterArg) { + this.stringList = setterArg; + return this; + } + + private @Nullable List intList; + + @CanIgnoreReturnValue + public @NonNull Builder setIntList(@Nullable List setterArg) { + this.intList = setterArg; + return this; + } + + private @Nullable List doubleList; + + @CanIgnoreReturnValue + public @NonNull Builder setDoubleList(@Nullable List setterArg) { + this.doubleList = setterArg; + return this; + } + + private @Nullable List boolList; + + @CanIgnoreReturnValue + public @NonNull Builder setBoolList(@Nullable List setterArg) { + this.boolList = setterArg; + return this; + } + + private @Nullable List nestedClassList; + + @CanIgnoreReturnValue + public @NonNull Builder setNestedClassList(@Nullable List setterArg) { + this.nestedClassList = setterArg; + return this; + } + + private @Nullable Map map; + + @CanIgnoreReturnValue + public @NonNull Builder setMap(@Nullable Map setterArg) { + this.map = setterArg; + return this; + } + public @NonNull AllNullableTypes build() { AllNullableTypes pigeonReturn = new AllNullableTypes(); pigeonReturn.setANullableBool(aNullableBool); @@ -771,8 +1076,6 @@ public static final class Builder { pigeonReturn.setANullable4ByteArray(aNullable4ByteArray); pigeonReturn.setANullable8ByteArray(aNullable8ByteArray); pigeonReturn.setANullableFloatArray(aNullableFloatArray); - pigeonReturn.setANullableList(aNullableList); - pigeonReturn.setANullableMap(aNullableMap); pigeonReturn.setNullableNestedList(nullableNestedList); pigeonReturn.setNullableMapWithAnnotations(nullableMapWithAnnotations); pigeonReturn.setNullableMapWithObject(nullableMapWithObject); @@ -780,13 +1083,20 @@ public static final class Builder { pigeonReturn.setANullableString(aNullableString); pigeonReturn.setANullableObject(aNullableObject); pigeonReturn.setAllNullableTypes(allNullableTypes); + pigeonReturn.setList(list); + pigeonReturn.setStringList(stringList); + pigeonReturn.setIntList(intList); + pigeonReturn.setDoubleList(doubleList); + pigeonReturn.setBoolList(boolList); + pigeonReturn.setNestedClassList(nestedClassList); + pigeonReturn.setMap(map); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(17); + ArrayList toListResult = new ArrayList(22); toListResult.add(aNullableBool); toListResult.add(aNullableInt); toListResult.add(aNullableInt64); @@ -795,66 +1105,77 @@ ArrayList toList() { toListResult.add(aNullable4ByteArray); toListResult.add(aNullable8ByteArray); toListResult.add(aNullableFloatArray); - toListResult.add(aNullableList); - toListResult.add(aNullableMap); toListResult.add(nullableNestedList); toListResult.add(nullableMapWithAnnotations); toListResult.add(nullableMapWithObject); - toListResult.add(aNullableEnum == null ? null : aNullableEnum.index); + toListResult.add(aNullableEnum); toListResult.add(aNullableString); toListResult.add(aNullableObject); - toListResult.add((allNullableTypes == null) ? null : allNullableTypes.toList()); + toListResult.add(allNullableTypes); + toListResult.add(list); + toListResult.add(stringList); + toListResult.add(intList); + toListResult.add(doubleList); + toListResult.add(boolList); + toListResult.add(nestedClassList); + toListResult.add(map); return toListResult; } - static @NonNull AllNullableTypes fromList(@NonNull ArrayList list) { + static @NonNull AllNullableTypes fromList(@NonNull ArrayList __pigeon_list) { AllNullableTypes pigeonResult = new AllNullableTypes(); - Object aNullableBool = list.get(0); + Object aNullableBool = __pigeon_list.get(0); pigeonResult.setANullableBool((Boolean) aNullableBool); - Object aNullableInt = list.get(1); + Object aNullableInt = __pigeon_list.get(1); pigeonResult.setANullableInt( (aNullableInt == null) ? null : ((aNullableInt instanceof Integer) ? (Integer) aNullableInt : (Long) aNullableInt)); - Object aNullableInt64 = list.get(2); + Object aNullableInt64 = __pigeon_list.get(2); pigeonResult.setANullableInt64( (aNullableInt64 == null) ? null : ((aNullableInt64 instanceof Integer) ? (Integer) aNullableInt64 : (Long) aNullableInt64)); - Object aNullableDouble = list.get(3); + Object aNullableDouble = __pigeon_list.get(3); pigeonResult.setANullableDouble((Double) aNullableDouble); - Object aNullableByteArray = list.get(4); + Object aNullableByteArray = __pigeon_list.get(4); pigeonResult.setANullableByteArray((byte[]) aNullableByteArray); - Object aNullable4ByteArray = list.get(5); + Object aNullable4ByteArray = __pigeon_list.get(5); pigeonResult.setANullable4ByteArray((int[]) aNullable4ByteArray); - Object aNullable8ByteArray = list.get(6); + Object aNullable8ByteArray = __pigeon_list.get(6); pigeonResult.setANullable8ByteArray((long[]) aNullable8ByteArray); - Object aNullableFloatArray = list.get(7); + Object aNullableFloatArray = __pigeon_list.get(7); pigeonResult.setANullableFloatArray((double[]) aNullableFloatArray); - Object aNullableList = list.get(8); - pigeonResult.setANullableList((List) aNullableList); - Object aNullableMap = list.get(9); - pigeonResult.setANullableMap((Map) aNullableMap); - Object nullableNestedList = list.get(10); + Object nullableNestedList = __pigeon_list.get(8); pigeonResult.setNullableNestedList((List>) nullableNestedList); - Object nullableMapWithAnnotations = list.get(11); + Object nullableMapWithAnnotations = __pigeon_list.get(9); pigeonResult.setNullableMapWithAnnotations((Map) nullableMapWithAnnotations); - Object nullableMapWithObject = list.get(12); + Object nullableMapWithObject = __pigeon_list.get(10); pigeonResult.setNullableMapWithObject((Map) nullableMapWithObject); - Object aNullableEnum = list.get(13); - pigeonResult.setANullableEnum( - aNullableEnum == null ? null : AnEnum.values()[(int) aNullableEnum]); - Object aNullableString = list.get(14); + Object aNullableEnum = __pigeon_list.get(11); + pigeonResult.setANullableEnum((AnEnum) aNullableEnum); + Object aNullableString = __pigeon_list.get(12); pigeonResult.setANullableString((String) aNullableString); - Object aNullableObject = list.get(15); + Object aNullableObject = __pigeon_list.get(13); pigeonResult.setANullableObject(aNullableObject); - Object allNullableTypes = list.get(16); - pigeonResult.setAllNullableTypes( - (allNullableTypes == null) - ? null - : AllNullableTypes.fromList((ArrayList) allNullableTypes)); + Object allNullableTypes = __pigeon_list.get(14); + pigeonResult.setAllNullableTypes((AllNullableTypes) allNullableTypes); + Object list = __pigeon_list.get(15); + pigeonResult.setList((List) list); + Object stringList = __pigeon_list.get(16); + pigeonResult.setStringList((List) stringList); + Object intList = __pigeon_list.get(17); + pigeonResult.setIntList((List) intList); + Object doubleList = __pigeon_list.get(18); + pigeonResult.setDoubleList((List) doubleList); + Object boolList = __pigeon_list.get(19); + pigeonResult.setBoolList((List) boolList); + Object nestedClassList = __pigeon_list.get(20); + pigeonResult.setNestedClassList((List) nestedClassList); + Object map = __pigeon_list.get(21); + pigeonResult.setMap((Map) map); return pigeonResult; } } @@ -946,26 +1267,6 @@ public void setANullableFloatArray(@Nullable double[] setterArg) { this.aNullableFloatArray = setterArg; } - private @Nullable List aNullableList; - - public @Nullable List getANullableList() { - return aNullableList; - } - - public void setANullableList(@Nullable List setterArg) { - this.aNullableList = setterArg; - } - - private @Nullable Map aNullableMap; - - public @Nullable Map getANullableMap() { - return aNullableMap; - } - - public void setANullableMap(@Nullable Map setterArg) { - this.aNullableMap = setterArg; - } - private @Nullable List> nullableNestedList; public @Nullable List> getNullableNestedList() { @@ -1026,6 +1327,124 @@ public void setANullableObject(@Nullable Object setterArg) { this.aNullableObject = setterArg; } + private @Nullable List list; + + public @Nullable List getList() { + return list; + } + + public void setList(@Nullable List setterArg) { + this.list = setterArg; + } + + private @Nullable List stringList; + + public @Nullable List getStringList() { + return stringList; + } + + public void setStringList(@Nullable List setterArg) { + this.stringList = setterArg; + } + + private @Nullable List intList; + + public @Nullable List getIntList() { + return intList; + } + + public void setIntList(@Nullable List setterArg) { + this.intList = setterArg; + } + + private @Nullable List doubleList; + + public @Nullable List getDoubleList() { + return doubleList; + } + + public void setDoubleList(@Nullable List setterArg) { + this.doubleList = setterArg; + } + + private @Nullable List boolList; + + public @Nullable List getBoolList() { + return boolList; + } + + public void setBoolList(@Nullable List setterArg) { + this.boolList = setterArg; + } + + private @Nullable Map map; + + public @Nullable Map getMap() { + return map; + } + + public void setMap(@Nullable Map setterArg) { + this.map = setterArg; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AllNullableTypesWithoutRecursion that = (AllNullableTypesWithoutRecursion) o; + return Objects.equals(aNullableBool, that.aNullableBool) + && Objects.equals(aNullableInt, that.aNullableInt) + && Objects.equals(aNullableInt64, that.aNullableInt64) + && Objects.equals(aNullableDouble, that.aNullableDouble) + && Arrays.equals(aNullableByteArray, that.aNullableByteArray) + && Arrays.equals(aNullable4ByteArray, that.aNullable4ByteArray) + && Arrays.equals(aNullable8ByteArray, that.aNullable8ByteArray) + && Arrays.equals(aNullableFloatArray, that.aNullableFloatArray) + && Objects.equals(nullableNestedList, that.nullableNestedList) + && Objects.equals(nullableMapWithAnnotations, that.nullableMapWithAnnotations) + && Objects.equals(nullableMapWithObject, that.nullableMapWithObject) + && Objects.equals(aNullableEnum, that.aNullableEnum) + && Objects.equals(aNullableString, that.aNullableString) + && Objects.equals(aNullableObject, that.aNullableObject) + && Objects.equals(list, that.list) + && Objects.equals(stringList, that.stringList) + && Objects.equals(intList, that.intList) + && Objects.equals(doubleList, that.doubleList) + && Objects.equals(boolList, that.boolList) + && Objects.equals(map, that.map); + } + + @Override + public int hashCode() { + int __pigeon_result = + Objects.hash( + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + nullableNestedList, + nullableMapWithAnnotations, + nullableMapWithObject, + aNullableEnum, + aNullableString, + aNullableObject, + list, + stringList, + intList, + doubleList, + boolList, + map); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable4ByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable8ByteArray); + __pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableFloatArray); + return __pigeon_result; + } + public static final class Builder { private @Nullable Boolean aNullableBool; @@ -1092,22 +1511,6 @@ public static final class Builder { return this; } - private @Nullable List aNullableList; - - @CanIgnoreReturnValue - public @NonNull Builder setANullableList(@Nullable List setterArg) { - this.aNullableList = setterArg; - return this; - } - - private @Nullable Map aNullableMap; - - @CanIgnoreReturnValue - public @NonNull Builder setANullableMap(@Nullable Map setterArg) { - this.aNullableMap = setterArg; - return this; - } - private @Nullable List> nullableNestedList; @CanIgnoreReturnValue @@ -1157,6 +1560,54 @@ public static final class Builder { return this; } + private @Nullable List list; + + @CanIgnoreReturnValue + public @NonNull Builder setList(@Nullable List setterArg) { + this.list = setterArg; + return this; + } + + private @Nullable List stringList; + + @CanIgnoreReturnValue + public @NonNull Builder setStringList(@Nullable List setterArg) { + this.stringList = setterArg; + return this; + } + + private @Nullable List intList; + + @CanIgnoreReturnValue + public @NonNull Builder setIntList(@Nullable List setterArg) { + this.intList = setterArg; + return this; + } + + private @Nullable List doubleList; + + @CanIgnoreReturnValue + public @NonNull Builder setDoubleList(@Nullable List setterArg) { + this.doubleList = setterArg; + return this; + } + + private @Nullable List boolList; + + @CanIgnoreReturnValue + public @NonNull Builder setBoolList(@Nullable List setterArg) { + this.boolList = setterArg; + return this; + } + + private @Nullable Map map; + + @CanIgnoreReturnValue + public @NonNull Builder setMap(@Nullable Map setterArg) { + this.map = setterArg; + return this; + } + public @NonNull AllNullableTypesWithoutRecursion build() { AllNullableTypesWithoutRecursion pigeonReturn = new AllNullableTypesWithoutRecursion(); pigeonReturn.setANullableBool(aNullableBool); @@ -1167,21 +1618,25 @@ public static final class Builder { pigeonReturn.setANullable4ByteArray(aNullable4ByteArray); pigeonReturn.setANullable8ByteArray(aNullable8ByteArray); pigeonReturn.setANullableFloatArray(aNullableFloatArray); - pigeonReturn.setANullableList(aNullableList); - pigeonReturn.setANullableMap(aNullableMap); pigeonReturn.setNullableNestedList(nullableNestedList); pigeonReturn.setNullableMapWithAnnotations(nullableMapWithAnnotations); pigeonReturn.setNullableMapWithObject(nullableMapWithObject); pigeonReturn.setANullableEnum(aNullableEnum); pigeonReturn.setANullableString(aNullableString); pigeonReturn.setANullableObject(aNullableObject); + pigeonReturn.setList(list); + pigeonReturn.setStringList(stringList); + pigeonReturn.setIntList(intList); + pigeonReturn.setDoubleList(doubleList); + pigeonReturn.setBoolList(boolList); + pigeonReturn.setMap(map); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(16); + ArrayList toListResult = new ArrayList(20); toListResult.add(aNullableBool); toListResult.add(aNullableInt); toListResult.add(aNullableInt64); @@ -1190,60 +1645,72 @@ ArrayList toList() { toListResult.add(aNullable4ByteArray); toListResult.add(aNullable8ByteArray); toListResult.add(aNullableFloatArray); - toListResult.add(aNullableList); - toListResult.add(aNullableMap); toListResult.add(nullableNestedList); toListResult.add(nullableMapWithAnnotations); toListResult.add(nullableMapWithObject); - toListResult.add(aNullableEnum == null ? null : aNullableEnum.index); + toListResult.add(aNullableEnum); toListResult.add(aNullableString); toListResult.add(aNullableObject); + toListResult.add(list); + toListResult.add(stringList); + toListResult.add(intList); + toListResult.add(doubleList); + toListResult.add(boolList); + toListResult.add(map); return toListResult; } - static @NonNull AllNullableTypesWithoutRecursion fromList(@NonNull ArrayList list) { + static @NonNull AllNullableTypesWithoutRecursion fromList( + @NonNull ArrayList __pigeon_list) { AllNullableTypesWithoutRecursion pigeonResult = new AllNullableTypesWithoutRecursion(); - Object aNullableBool = list.get(0); + Object aNullableBool = __pigeon_list.get(0); pigeonResult.setANullableBool((Boolean) aNullableBool); - Object aNullableInt = list.get(1); + Object aNullableInt = __pigeon_list.get(1); pigeonResult.setANullableInt( (aNullableInt == null) ? null : ((aNullableInt instanceof Integer) ? (Integer) aNullableInt : (Long) aNullableInt)); - Object aNullableInt64 = list.get(2); + Object aNullableInt64 = __pigeon_list.get(2); pigeonResult.setANullableInt64( (aNullableInt64 == null) ? null : ((aNullableInt64 instanceof Integer) ? (Integer) aNullableInt64 : (Long) aNullableInt64)); - Object aNullableDouble = list.get(3); + Object aNullableDouble = __pigeon_list.get(3); pigeonResult.setANullableDouble((Double) aNullableDouble); - Object aNullableByteArray = list.get(4); + Object aNullableByteArray = __pigeon_list.get(4); pigeonResult.setANullableByteArray((byte[]) aNullableByteArray); - Object aNullable4ByteArray = list.get(5); + Object aNullable4ByteArray = __pigeon_list.get(5); pigeonResult.setANullable4ByteArray((int[]) aNullable4ByteArray); - Object aNullable8ByteArray = list.get(6); + Object aNullable8ByteArray = __pigeon_list.get(6); pigeonResult.setANullable8ByteArray((long[]) aNullable8ByteArray); - Object aNullableFloatArray = list.get(7); + Object aNullableFloatArray = __pigeon_list.get(7); pigeonResult.setANullableFloatArray((double[]) aNullableFloatArray); - Object aNullableList = list.get(8); - pigeonResult.setANullableList((List) aNullableList); - Object aNullableMap = list.get(9); - pigeonResult.setANullableMap((Map) aNullableMap); - Object nullableNestedList = list.get(10); + Object nullableNestedList = __pigeon_list.get(8); pigeonResult.setNullableNestedList((List>) nullableNestedList); - Object nullableMapWithAnnotations = list.get(11); + Object nullableMapWithAnnotations = __pigeon_list.get(9); pigeonResult.setNullableMapWithAnnotations((Map) nullableMapWithAnnotations); - Object nullableMapWithObject = list.get(12); + Object nullableMapWithObject = __pigeon_list.get(10); pigeonResult.setNullableMapWithObject((Map) nullableMapWithObject); - Object aNullableEnum = list.get(13); - pigeonResult.setANullableEnum( - aNullableEnum == null ? null : AnEnum.values()[(int) aNullableEnum]); - Object aNullableString = list.get(14); + Object aNullableEnum = __pigeon_list.get(11); + pigeonResult.setANullableEnum((AnEnum) aNullableEnum); + Object aNullableString = __pigeon_list.get(12); pigeonResult.setANullableString((String) aNullableString); - Object aNullableObject = list.get(15); + Object aNullableObject = __pigeon_list.get(13); pigeonResult.setANullableObject(aNullableObject); + Object list = __pigeon_list.get(14); + pigeonResult.setList((List) list); + Object stringList = __pigeon_list.get(15); + pigeonResult.setStringList((List) stringList); + Object intList = __pigeon_list.get(16); + pigeonResult.setIntList((List) intList); + Object doubleList = __pigeon_list.get(17); + pigeonResult.setDoubleList((List) doubleList); + Object boolList = __pigeon_list.get(18); + pigeonResult.setBoolList((List) boolList); + Object map = __pigeon_list.get(19); + pigeonResult.setMap((Map) map); return pigeonResult; } } @@ -1295,6 +1762,25 @@ public void setAllTypes(@Nullable AllTypes setterArg) { /** Constructor is non-public to enforce null safety; use Builder. */ AllClassesWrapper() {} + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AllClassesWrapper that = (AllClassesWrapper) o; + return allNullableTypes.equals(that.allNullableTypes) + && Objects.equals(allNullableTypesWithoutRecursion, that.allNullableTypesWithoutRecursion) + && Objects.equals(allTypes, that.allTypes); + } + + @Override + public int hashCode() { + return Objects.hash(allNullableTypes, allNullableTypesWithoutRecursion, allTypes); + } + public static final class Builder { private @Nullable AllNullableTypes allNullableTypes; @@ -1334,31 +1820,21 @@ public static final class Builder { @NonNull ArrayList toList() { ArrayList toListResult = new ArrayList(3); - toListResult.add((allNullableTypes == null) ? null : allNullableTypes.toList()); - toListResult.add( - (allNullableTypesWithoutRecursion == null) - ? null - : allNullableTypesWithoutRecursion.toList()); - toListResult.add((allTypes == null) ? null : allTypes.toList()); + toListResult.add(allNullableTypes); + toListResult.add(allNullableTypesWithoutRecursion); + toListResult.add(allTypes); return toListResult; } - static @NonNull AllClassesWrapper fromList(@NonNull ArrayList list) { + static @NonNull AllClassesWrapper fromList(@NonNull ArrayList __pigeon_list) { AllClassesWrapper pigeonResult = new AllClassesWrapper(); - Object allNullableTypes = list.get(0); - pigeonResult.setAllNullableTypes( - (allNullableTypes == null) - ? null - : AllNullableTypes.fromList((ArrayList) allNullableTypes)); - Object allNullableTypesWithoutRecursion = list.get(1); + Object allNullableTypes = __pigeon_list.get(0); + pigeonResult.setAllNullableTypes((AllNullableTypes) allNullableTypes); + Object allNullableTypesWithoutRecursion = __pigeon_list.get(1); pigeonResult.setAllNullableTypesWithoutRecursion( - (allNullableTypesWithoutRecursion == null) - ? null - : AllNullableTypesWithoutRecursion.fromList( - (ArrayList) allNullableTypesWithoutRecursion)); - Object allTypes = list.get(2); - pigeonResult.setAllTypes( - (allTypes == null) ? null : AllTypes.fromList((ArrayList) allTypes)); + (AllNullableTypesWithoutRecursion) allNullableTypesWithoutRecursion); + Object allTypes = __pigeon_list.get(2); + pigeonResult.setAllTypes((AllTypes) allTypes); return pigeonResult; } } @@ -1379,6 +1855,23 @@ public void setTestList(@Nullable List setterArg) { this.testList = setterArg; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TestMessage that = (TestMessage) o; + return Objects.equals(testList, that.testList); + } + + @Override + public int hashCode() { + return Objects.hash(testList); + } + public static final class Builder { private @Nullable List testList; @@ -1403,57 +1896,35 @@ ArrayList toList() { return toListResult; } - static @NonNull TestMessage fromList(@NonNull ArrayList list) { + static @NonNull TestMessage fromList(@NonNull ArrayList __pigeon_list) { TestMessage pigeonResult = new TestMessage(); - Object testList = list.get(0); + Object testList = __pigeon_list.get(0); pigeonResult.setTestList((List) testList); return pigeonResult; } } - /** Asynchronous error handling return type for non-nullable API method returns. */ - public interface Result { - /** Success case callback method for handling returns. */ - void success(@NonNull T result); + private static class PigeonCodec extends StandardMessageCodec { + public static final PigeonCodec INSTANCE = new PigeonCodec(); - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for nullable API method returns. */ - public interface NullableResult { - /** Success case callback method for handling returns. */ - void success(@Nullable T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for void API method returns. */ - public interface VoidResult { - /** Success case callback method for handling returns. */ - void success(); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - - private static class HostIntegrationCoreApiCodec extends StandardMessageCodec { - public static final HostIntegrationCoreApiCodec INSTANCE = new HostIntegrationCoreApiCodec(); - - private HostIntegrationCoreApiCodec() {} + private PigeonCodec() {} @Override protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { - case (byte) 128: - return AllClassesWrapper.fromList((ArrayList) readValue(buffer)); case (byte) 129: - return AllNullableTypes.fromList((ArrayList) readValue(buffer)); + return AllTypes.fromList((ArrayList) readValue(buffer)); case (byte) 130: - return AllNullableTypesWithoutRecursion.fromList((ArrayList) readValue(buffer)); + return AllNullableTypes.fromList((ArrayList) readValue(buffer)); case (byte) 131: - return AllTypes.fromList((ArrayList) readValue(buffer)); + return AllNullableTypesWithoutRecursion.fromList((ArrayList) readValue(buffer)); case (byte) 132: + return AllClassesWrapper.fromList((ArrayList) readValue(buffer)); + case (byte) 133: return TestMessage.fromList((ArrayList) readValue(buffer)); + case (byte) 134: + Object value = readValue(buffer); + return value == null ? null : AnEnum.values()[(int) value]; default: return super.readValueOfType(type, buffer); } @@ -1461,27 +1932,54 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof AllClassesWrapper) { - stream.write(128); - writeValue(stream, ((AllClassesWrapper) value).toList()); - } else if (value instanceof AllNullableTypes) { + if (value instanceof AllTypes) { stream.write(129); + writeValue(stream, ((AllTypes) value).toList()); + } else if (value instanceof AllNullableTypes) { + stream.write(130); writeValue(stream, ((AllNullableTypes) value).toList()); } else if (value instanceof AllNullableTypesWithoutRecursion) { - stream.write(130); - writeValue(stream, ((AllNullableTypesWithoutRecursion) value).toList()); - } else if (value instanceof AllTypes) { stream.write(131); - writeValue(stream, ((AllTypes) value).toList()); - } else if (value instanceof TestMessage) { + writeValue(stream, ((AllNullableTypesWithoutRecursion) value).toList()); + } else if (value instanceof AllClassesWrapper) { stream.write(132); + writeValue(stream, ((AllClassesWrapper) value).toList()); + } else if (value instanceof TestMessage) { + stream.write(133); writeValue(stream, ((TestMessage) value).toList()); + } else if (value instanceof AnEnum) { + stream.write(134); + writeValue(stream, value == null ? null : ((AnEnum) value).index); } else { super.writeValue(stream, value); } } } + /** Asynchronous error handling return type for non-nullable API method returns. */ + public interface Result { + /** Success case callback method for handling returns. */ + void success(@NonNull T result); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** Asynchronous error handling return type for nullable API method returns. */ + public interface NullableResult { + /** Success case callback method for handling returns. */ + void success(@Nullable T result); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** Asynchronous error handling return type for void API method returns. */ + public interface VoidResult { + /** Success case callback method for handling returns. */ + void success(); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } /** * The core interface that each host language plugin must implement in platform_test integration * tests. @@ -1524,7 +2022,7 @@ public interface HostIntegrationCoreApi { Object echoObject(@NonNull Object anObject); /** Returns the passed list, to test serialization and deserialization. */ @NonNull - List echoList(@NonNull List aList); + List echoList(@NonNull List list); /** Returns the passed map, to test serialization and deserialization. */ @NonNull Map echoMap(@NonNull Map aMap); @@ -1623,7 +2121,7 @@ AllNullableTypesWithoutRecursion sendMultipleNullableTypesWithoutRecursion( /** Returns the passed in generic Object asynchronously. */ void echoAsyncObject(@NonNull Object anObject, @NonNull Result result); /** Returns the passed list, to test asynchronous serialization and deserialization. */ - void echoAsyncList(@NonNull List aList, @NonNull Result> result); + void echoAsyncList(@NonNull List list, @NonNull Result> result); /** Returns the passed map, to test asynchronous serialization and deserialization. */ void echoAsyncMap( @NonNull Map aMap, @NonNull Result> result); @@ -1659,7 +2157,7 @@ void echoAsyncNullableUint8List( void echoAsyncNullableObject(@Nullable Object anObject, @NonNull NullableResult result); /** Returns the passed list, to test asynchronous serialization and deserialization. */ void echoAsyncNullableList( - @Nullable List aList, @NonNull NullableResult> result); + @Nullable List list, @NonNull NullableResult> result); /** Returns the passed map, to test asynchronous serialization and deserialization. */ void echoAsyncNullableMap( @Nullable Map aMap, @NonNull NullableResult> result); @@ -1701,9 +2199,9 @@ void callFlutterSendMultipleNullableTypesWithoutRecursion( void callFlutterEchoString(@NonNull String aString, @NonNull Result result); - void callFlutterEchoUint8List(@NonNull byte[] aList, @NonNull Result result); + void callFlutterEchoUint8List(@NonNull byte[] list, @NonNull Result result); - void callFlutterEchoList(@NonNull List aList, @NonNull Result> result); + void callFlutterEchoList(@NonNull List list, @NonNull Result> result); void callFlutterEchoMap( @NonNull Map aMap, @NonNull Result> result); @@ -1722,10 +2220,10 @@ void callFlutterEchoNullableString( @Nullable String aString, @NonNull NullableResult result); void callFlutterEchoNullableUint8List( - @Nullable byte[] aList, @NonNull NullableResult result); + @Nullable byte[] list, @NonNull NullableResult result); void callFlutterEchoNullableList( - @Nullable List aList, @NonNull NullableResult> result); + @Nullable List list, @NonNull NullableResult> result); void callFlutterEchoNullableMap( @Nullable Map aMap, @NonNull NullableResult> result); @@ -1737,7 +2235,7 @@ void callFlutterEchoNullableEnum( /** The codec used by HostIntegrationCoreApi. */ static @NonNull MessageCodec getCodec() { - return HostIntegrationCoreApiCodec.INSTANCE; + return PigeonCodec.INSTANCE; } /** * Sets up an instance of `HostIntegrationCoreApi` to handle messages through the @@ -2043,9 +2541,9 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - List aListArg = (List) args.get(0); + List listArg = (List) args.get(0); try { - List output = api.echoList(aListArg); + List output = api.echoList(listArg); wrapped.add(0, output); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -2121,10 +2619,10 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - AnEnum anEnumArg = AnEnum.values()[(int) args.get(0)]; + AnEnum anEnumArg = (AnEnum) args.get(0); try { AnEnum output = api.echoEnum(anEnumArg); - wrapped.add(0, output.index); + wrapped.add(0, output); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; @@ -2606,10 +3104,10 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - AnEnum anEnumArg = args.get(0) == null ? null : AnEnum.values()[(int) args.get(0)]; + AnEnum anEnumArg = (AnEnum) args.get(0); try { AnEnum output = api.echoNullableEnum(anEnumArg); - wrapped.add(0, output == null ? null : output.index); + wrapped.add(0, output); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; @@ -2908,7 +3406,7 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - List aListArg = (List) args.get(0); + List listArg = (List) args.get(0); Result> resultCallback = new Result>() { public void success(List result) { @@ -2922,7 +3420,7 @@ public void error(Throwable error) { } }; - api.echoAsyncList(aListArg, resultCallback); + api.echoAsyncList(listArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -2972,11 +3470,11 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - AnEnum anEnumArg = AnEnum.values()[(int) args.get(0)]; + AnEnum anEnumArg = (AnEnum) args.get(0); Result resultCallback = new Result() { public void success(AnEnum result) { - wrapped.add(0, result.index); + wrapped.add(0, result); reply.reply(wrapped); } @@ -3385,7 +3883,7 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - List aListArg = (List) args.get(0); + List listArg = (List) args.get(0); NullableResult> resultCallback = new NullableResult>() { public void success(List result) { @@ -3399,7 +3897,7 @@ public void error(Throwable error) { } }; - api.echoAsyncNullableList(aListArg, resultCallback); + api.echoAsyncNullableList(listArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -3449,11 +3947,11 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - AnEnum anEnumArg = args.get(0) == null ? null : AnEnum.values()[(int) args.get(0)]; + AnEnum anEnumArg = (AnEnum) args.get(0); NullableResult resultCallback = new NullableResult() { public void success(AnEnum result) { - wrapped.add(0, result == null ? null : result.index); + wrapped.add(0, result); reply.reply(wrapped); } @@ -3873,7 +4371,7 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - byte[] aListArg = (byte[]) args.get(0); + byte[] listArg = (byte[]) args.get(0); Result resultCallback = new Result() { public void success(byte[] result) { @@ -3887,7 +4385,7 @@ public void error(Throwable error) { } }; - api.callFlutterEchoUint8List(aListArg, resultCallback); + api.callFlutterEchoUint8List(listArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -3905,7 +4403,7 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - List aListArg = (List) args.get(0); + List listArg = (List) args.get(0); Result> resultCallback = new Result>() { public void success(List result) { @@ -3919,7 +4417,7 @@ public void error(Throwable error) { } }; - api.callFlutterEchoList(aListArg, resultCallback); + api.callFlutterEchoList(listArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -3969,11 +4467,11 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - AnEnum anEnumArg = AnEnum.values()[(int) args.get(0)]; + AnEnum anEnumArg = (AnEnum) args.get(0); Result resultCallback = new Result() { public void success(AnEnum result) { - wrapped.add(0, result.index); + wrapped.add(0, result); reply.reply(wrapped); } @@ -4130,7 +4628,7 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - byte[] aListArg = (byte[]) args.get(0); + byte[] listArg = (byte[]) args.get(0); NullableResult resultCallback = new NullableResult() { public void success(byte[] result) { @@ -4144,7 +4642,7 @@ public void error(Throwable error) { } }; - api.callFlutterEchoNullableUint8List(aListArg, resultCallback); + api.callFlutterEchoNullableUint8List(listArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -4162,7 +4660,7 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - List aListArg = (List) args.get(0); + List listArg = (List) args.get(0); NullableResult> resultCallback = new NullableResult>() { public void success(List result) { @@ -4176,7 +4674,7 @@ public void error(Throwable error) { } }; - api.callFlutterEchoNullableList(aListArg, resultCallback); + api.callFlutterEchoNullableList(listArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -4226,11 +4724,11 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - AnEnum anEnumArg = args.get(0) == null ? null : AnEnum.values()[(int) args.get(0)]; + AnEnum anEnumArg = (AnEnum) args.get(0); NullableResult resultCallback = new NullableResult() { public void success(AnEnum result) { - wrapped.add(0, result == null ? null : result.index); + wrapped.add(0, result); reply.reply(wrapped); } @@ -4280,54 +4778,6 @@ public void error(Throwable error) { } } } - - private static class FlutterIntegrationCoreApiCodec extends StandardMessageCodec { - public static final FlutterIntegrationCoreApiCodec INSTANCE = - new FlutterIntegrationCoreApiCodec(); - - private FlutterIntegrationCoreApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return AllClassesWrapper.fromList((ArrayList) readValue(buffer)); - case (byte) 129: - return AllNullableTypes.fromList((ArrayList) readValue(buffer)); - case (byte) 130: - return AllNullableTypesWithoutRecursion.fromList((ArrayList) readValue(buffer)); - case (byte) 131: - return AllTypes.fromList((ArrayList) readValue(buffer)); - case (byte) 132: - return TestMessage.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof AllClassesWrapper) { - stream.write(128); - writeValue(stream, ((AllClassesWrapper) value).toList()); - } else if (value instanceof AllNullableTypes) { - stream.write(129); - writeValue(stream, ((AllNullableTypes) value).toList()); - } else if (value instanceof AllNullableTypesWithoutRecursion) { - stream.write(130); - writeValue(stream, ((AllNullableTypesWithoutRecursion) value).toList()); - } else if (value instanceof AllTypes) { - stream.write(131); - writeValue(stream, ((AllTypes) value).toList()); - } else if (value instanceof TestMessage) { - stream.write(132); - writeValue(stream, ((TestMessage) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - /** * The core interface that the Dart platform_test code implements for host integration tests to * call into. @@ -4351,7 +4801,7 @@ public FlutterIntegrationCoreApi( /** Public interface for sending reply. */ /** The codec used by FlutterIntegrationCoreApi. */ static @NonNull MessageCodec getCodec() { - return FlutterIntegrationCoreApiCodec.INSTANCE; + return PigeonCodec.INSTANCE; } /** * A no-op function taking no arguments and returning no value, to sanity test basic calling. @@ -4755,14 +5205,14 @@ public void echoString(@NonNull String aStringArg, @NonNull Result resul }); } /** Returns the passed byte list, to test serialization and deserialization. */ - public void echoUint8List(@NonNull byte[] aListArg, @NonNull Result result) { + public void echoUint8List(@NonNull byte[] listArg, @NonNull Result result) { final String channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoUint8List" + messageChannelSuffix; BasicMessageChannel channel = new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); channel.send( - new ArrayList(Collections.singletonList(aListArg)), + new ArrayList(Collections.singletonList(listArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; @@ -4789,14 +5239,14 @@ public void echoUint8List(@NonNull byte[] aListArg, @NonNull Result resu }); } /** Returns the passed list, to test serialization and deserialization. */ - public void echoList(@NonNull List aListArg, @NonNull Result> result) { + public void echoList(@NonNull List listArg, @NonNull Result> result) { final String channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoList" + messageChannelSuffix; BasicMessageChannel channel = new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); channel.send( - new ArrayList(Collections.singletonList(aListArg)), + new ArrayList(Collections.singletonList(listArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; @@ -4865,7 +5315,7 @@ public void echoEnum(@NonNull AnEnum anEnumArg, @NonNull Result result) BasicMessageChannel channel = new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); channel.send( - new ArrayList(Collections.singletonList(anEnumArg.index)), + new ArrayList(Collections.singletonList(anEnumArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; @@ -4883,7 +5333,7 @@ public void echoEnum(@NonNull AnEnum anEnumArg, @NonNull Result result) "")); } else { @SuppressWarnings("ConstantConditions") - AnEnum output = AnEnum.values()[(int) listReply.get(0)]; + AnEnum output = (AnEnum) listReply.get(0); result.success(output); } } else { @@ -5009,14 +5459,14 @@ public void echoNullableString( } /** Returns the passed byte list, to test serialization and deserialization. */ public void echoNullableUint8List( - @Nullable byte[] aListArg, @NonNull NullableResult result) { + @Nullable byte[] listArg, @NonNull NullableResult result) { final String channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableUint8List" + messageChannelSuffix; BasicMessageChannel channel = new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); channel.send( - new ArrayList(Collections.singletonList(aListArg)), + new ArrayList(Collections.singletonList(listArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; @@ -5038,14 +5488,14 @@ public void echoNullableUint8List( } /** Returns the passed list, to test serialization and deserialization. */ public void echoNullableList( - @Nullable List aListArg, @NonNull NullableResult> result) { + @Nullable List listArg, @NonNull NullableResult> result) { final String channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableList" + messageChannelSuffix; BasicMessageChannel channel = new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); channel.send( - new ArrayList(Collections.singletonList(aListArg)), + new ArrayList(Collections.singletonList(listArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; @@ -5104,8 +5554,7 @@ public void echoNullableEnum( BasicMessageChannel channel = new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); channel.send( - new ArrayList( - Collections.singletonList(anEnumArg == null ? null : anEnumArg.index)), + new ArrayList(Collections.singletonList(anEnumArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; @@ -5117,8 +5566,7 @@ public void echoNullableEnum( (String) listReply.get(2))); } else { @SuppressWarnings("ConstantConditions") - AnEnum output = - listReply.get(0) == null ? null : AnEnum.values()[(int) listReply.get(0)]; + AnEnum output = (AnEnum) listReply.get(0); result.success(output); } } else { @@ -5201,7 +5649,7 @@ public interface HostTrivialApi { /** The codec used by HostTrivialApi. */ static @NonNull MessageCodec getCodec() { - return new StandardMessageCodec(); + return PigeonCodec.INSTANCE; } /** Sets up an instance of `HostTrivialApi` to handle messages through the `binaryMessenger`. */ static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable HostTrivialApi api) { @@ -5252,7 +5700,7 @@ public interface HostSmallApi { /** The codec used by HostSmallApi. */ static @NonNull MessageCodec getCodec() { - return new StandardMessageCodec(); + return PigeonCodec.INSTANCE; } /** Sets up an instance of `HostSmallApi` to handle messages through the `binaryMessenger`. */ static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable HostSmallApi api) { @@ -5328,33 +5776,6 @@ public void error(Throwable error) { } } } - - private static class FlutterSmallApiCodec extends StandardMessageCodec { - public static final FlutterSmallApiCodec INSTANCE = new FlutterSmallApiCodec(); - - private FlutterSmallApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return TestMessage.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof TestMessage) { - stream.write(128); - writeValue(stream, ((TestMessage) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - /** * A simple API called in some unit tests. * @@ -5377,7 +5798,7 @@ public FlutterSmallApi( /** Public interface for sending reply. */ /** The codec used by FlutterSmallApi. */ static @NonNull MessageCodec getCodec() { - return FlutterSmallApiCodec.INSTANCE; + return PigeonCodec.INSTANCE; } public void echoWrappedList(@NonNull TestMessage msgArg, @NonNull Result result) { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java index 6f2fdf16e90..20146fdc0e0 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java @@ -24,22 +24,30 @@ void compareAllTypes(AllTypes firstTypes, AllTypes secondTypes) { if (firstTypes == null || secondTypes == null) { return; } + // Check all the fields individually to ensure that everything is as expected. assertEquals(firstTypes.getABool(), secondTypes.getABool()); assertEquals(firstTypes.getAnInt(), secondTypes.getAnInt()); assertEquals(firstTypes.getAnInt64(), secondTypes.getAnInt64()); - assertEquals(firstTypes.getADouble(), secondTypes.getADouble()); assertArrayEquals(firstTypes.getAByteArray(), secondTypes.getAByteArray()); assertArrayEquals(firstTypes.getA4ByteArray(), secondTypes.getA4ByteArray()); assertArrayEquals(firstTypes.getA8ByteArray(), secondTypes.getA8ByteArray()); assertTrue(floatArraysEqual(firstTypes.getAFloatArray(), secondTypes.getAFloatArray())); - assertArrayEquals(firstTypes.getAList().toArray(), secondTypes.getAList().toArray()); - assertArrayEquals( - firstTypes.getAMap().keySet().toArray(), secondTypes.getAMap().keySet().toArray()); - assertArrayEquals( - firstTypes.getAMap().values().toArray(), secondTypes.getAMap().values().toArray()); assertEquals(firstTypes.getAnEnum(), secondTypes.getAnEnum()); assertEquals(firstTypes.getAnObject(), secondTypes.getAnObject()); + assertArrayEquals(firstTypes.getList().toArray(), secondTypes.getList().toArray()); + assertArrayEquals(firstTypes.getStringList().toArray(), secondTypes.getStringList().toArray()); + assertArrayEquals(firstTypes.getBoolList().toArray(), secondTypes.getBoolList().toArray()); + assertArrayEquals(firstTypes.getDoubleList().toArray(), secondTypes.getDoubleList().toArray()); + assertArrayEquals(firstTypes.getIntList().toArray(), secondTypes.getIntList().toArray()); + assertArrayEquals( + firstTypes.getMap().keySet().toArray(), secondTypes.getMap().keySet().toArray()); + assertArrayEquals( + firstTypes.getMap().values().toArray(), secondTypes.getMap().values().toArray()); + + // Also check that the implementation of equality works. + assertEquals(firstTypes, secondTypes); + assertEquals(firstTypes.hashCode(), secondTypes.hashCode()); } void compareAllNullableTypes(AllNullableTypes firstTypes, AllNullableTypes secondTypes) { @@ -47,6 +55,7 @@ void compareAllNullableTypes(AllNullableTypes firstTypes, AllNullableTypes secon if (firstTypes == null || secondTypes == null) { return; } + // Check all the fields individually to ensure that everything is as expected. assertEquals(firstTypes.getANullableBool(), secondTypes.getANullableBool()); assertEquals(firstTypes.getANullableInt(), secondTypes.getANullableInt()); assertEquals(firstTypes.getANullableDouble(), secondTypes.getANullableDouble()); @@ -57,18 +66,23 @@ void compareAllNullableTypes(AllNullableTypes firstTypes, AllNullableTypes secon assertTrue( floatArraysEqual( firstTypes.getANullableFloatArray(), secondTypes.getANullableFloatArray())); - assertArrayEquals( - firstTypes.getANullableList().toArray(), secondTypes.getANullableList().toArray()); - assertArrayEquals( - firstTypes.getANullableMap().keySet().toArray(), - secondTypes.getANullableMap().keySet().toArray()); - assertArrayEquals( - firstTypes.getANullableMap().values().toArray(), - secondTypes.getANullableMap().values().toArray()); assertArrayEquals( firstTypes.getNullableMapWithObject().values().toArray(), secondTypes.getNullableMapWithObject().values().toArray()); assertEquals(firstTypes.getANullableObject(), secondTypes.getANullableObject()); + assertArrayEquals(firstTypes.getList().toArray(), secondTypes.getList().toArray()); + assertArrayEquals(firstTypes.getStringList().toArray(), secondTypes.getStringList().toArray()); + assertArrayEquals(firstTypes.getBoolList().toArray(), secondTypes.getBoolList().toArray()); + assertArrayEquals(firstTypes.getDoubleList().toArray(), secondTypes.getDoubleList().toArray()); + assertArrayEquals(firstTypes.getIntList().toArray(), secondTypes.getIntList().toArray()); + assertArrayEquals( + firstTypes.getMap().keySet().toArray(), secondTypes.getMap().keySet().toArray()); + assertArrayEquals( + firstTypes.getMap().values().toArray(), secondTypes.getMap().values().toArray()); + + // Also check that the implementation of equality works. + assertEquals(firstTypes, secondTypes); + assertEquals(firstTypes.hashCode(), secondTypes.hashCode()); } @Test @@ -105,9 +119,13 @@ public void success(AllNullableTypes result) { assertNull(everything.getANullable4ByteArray()); assertNull(everything.getANullable8ByteArray()); assertNull(everything.getANullableFloatArray()); - assertNull(everything.getANullableList()); - assertNull(everything.getANullableMap()); assertNull(everything.getNullableMapWithObject()); + assertNull(everything.getList()); + assertNull(everything.getDoubleList()); + assertNull(everything.getIntList()); + assertNull(everything.getStringList()); + assertNull(everything.getBoolList()); + assertNull(everything.getMap()); } public void error(Throwable error) { @@ -143,6 +161,8 @@ private static boolean floatArraysEqual(double[] x, double[] y) { @Test public void hasValues() { + // Not inline due to warnings about an ambiguous varargs call when inline. + final Object[] genericList = new Boolean[] {true, false}; AllTypes allEverything = new AllTypes.Builder() .setABool(false) @@ -154,10 +174,14 @@ public void hasValues() { .setA4ByteArray(new int[] {1, 2, 3, 4}) .setA8ByteArray(new long[] {1, 2, 3, 4}) .setAFloatArray(new double[] {0.5, 0.25, 1.5, 1.25}) - .setAList(Arrays.asList(new int[] {1, 2, 3})) - .setAMap(makeMap("hello", 1234)) .setAnEnum(CoreTests.AnEnum.ONE) .setAnObject(0) + .setBoolList(Arrays.asList(new Boolean[] {true, false})) + .setDoubleList(Arrays.asList(new Double[] {0.5, 0.25, 1.5, 1.25})) + .setIntList(Arrays.asList(new Long[] {1l, 2l, 3l, 4l})) + .setList(Arrays.asList(genericList)) + .setStringList(Arrays.asList(new String[] {"string", "another one"})) + .setMap(makeMap("hello", 1234)) .build(); AllNullableTypes everything = @@ -170,10 +194,14 @@ public void hasValues() { .setANullable4ByteArray(new int[] {1, 2, 3, 4}) .setANullable8ByteArray(new long[] {1, 2, 3, 4}) .setANullableFloatArray(new double[] {0.5, 0.25, 1.5, 1.25}) - .setANullableList(Arrays.asList(new int[] {1, 2, 3})) - .setANullableMap(makeMap("hello", 1234)) .setNullableMapWithObject(makeStringMap("hello", 1234)) .setANullableObject(0) + .setBoolList(Arrays.asList(new Boolean[] {true, false})) + .setDoubleList(Arrays.asList(new Double[] {0.5, 0.25, 1.5, 1.25})) + .setIntList(Arrays.asList(new Long[] {1l, 2l, 3l, 4l})) + .setList(Arrays.asList(genericList)) + .setStringList(Arrays.asList(new String[] {"string", "another one"})) + .setMap(makeMap("hello", 1234)) .build(); BinaryMessenger binaryMessenger = mock(BinaryMessenger.class); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/NullFieldsTest.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/NullFieldsTest.java index d581c2d8bc2..545ab13121d 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/NullFieldsTest.java +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/NullFieldsTest.java @@ -81,20 +81,23 @@ public void requestFromMapWithNulls() { @Test public void replyFromMapWithValues() { - ArrayList requestList = new ArrayList(); - - requestList.add("hello"); - requestList.add(1L); - - ArrayList list = new ArrayList(); + NullFields.NullFieldsSearchRequest request = + new NullFields.NullFieldsSearchRequest.Builder() + .setQuery("hello") + .setIdentifier(1L) + .build(); - list.add("result"); - list.add("error"); - list.add(Arrays.asList(1L, 2L, 3L)); - list.add(requestList); - list.add(NullFields.NullFieldsSearchReplyType.SUCCESS.ordinal()); + NullFields.NullFieldsSearchReply input = + new NullFields.NullFieldsSearchReply.Builder() + .setResult("result") + .setError("error") + .setIndices(Arrays.asList(1L, 2L, 3L)) + .setRequest(request) + .setType(NullFields.NullFieldsSearchReplyType.SUCCESS) + .build(); - NullFields.NullFieldsSearchReply reply = NullFields.NullFieldsSearchReply.fromList(list); + NullFields.NullFieldsSearchReply reply = + NullFields.NullFieldsSearchReply.fromList(input.toList()); assertEquals(reply.getResult(), "result"); assertEquals(reply.getError(), "error"); assertEquals(reply.getIndices(), Arrays.asList(1L, 2L, 3L)); @@ -143,16 +146,18 @@ public void requestToMapWithNulls() { @Test public void replyToMapWithValues() { + NullFields.NullFieldsSearchRequest request = + new NullFields.NullFieldsSearchRequest.Builder() + .setQuery("hello") + .setIdentifier(1L) + .build(); + NullFields.NullFieldsSearchReply reply = new NullFields.NullFieldsSearchReply.Builder() .setResult("result") .setError("error") .setIndices(Arrays.asList(1L, 2L, 3L)) - .setRequest( - new NullFields.NullFieldsSearchRequest.Builder() - .setQuery("hello") - .setIdentifier(1L) - .build()) + .setRequest(request) .setType(NullFields.NullFieldsSearchReplyType.SUCCESS) .build(); @@ -160,8 +165,8 @@ public void replyToMapWithValues() { assertEquals(list.get(0), "result"); assertEquals(list.get(1), "error"); assertEquals(list.get(2), Arrays.asList(1L, 2L, 3L)); - assertEquals(list.get(3), reply.getRequest().toList()); - assertEquals(list.get(4), NullFields.NullFieldsSearchReplyType.SUCCESS.ordinal()); + assertEquals(list.get(3), reply.getRequest()); + assertEquals(list.get(4), NullFields.NullFieldsSearchReplyType.SUCCESS); } @Test diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/.gitignore b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/.gitignore +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/build.gradle b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/build.gradle index 3c7f9bd24a2..18a1d3ccb47 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/build.gradle +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/settings.gradle b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/settings.gradle +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj index 4fced65c686..d922c88e3d4 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj @@ -266,7 +266,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33AA165E291EB8B600ECBEEB = { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 28b953fd126..d19488d45ea 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ *outputList = [binaryMessenger.codec decode:data]; XCTAssertEqualObjects(outputList[0], [NSNull null]); [expectation fulfill]; }); @@ -75,7 +75,7 @@ - (void)testAsyncFlutter2HostVoidVoid { - (void)testAsyncFlutter2HostVoidVoidError { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTGetCoreTestsCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; mockHostSmallApi.voidVoidError = [FlutterError errorWithCode:@"code" message:@"message" @@ -86,7 +86,7 @@ - (void)testAsyncFlutter2HostVoidVoidError { XCTestExpectation *expectation = [self expectationWithDescription:@"voidvoid callback"]; binaryMessenger.handlers[channelName](nil, ^(NSData *data) { - NSArray *outputList = [binaryMessenger.codec decode:data]; + NSArray *outputList = [binaryMessenger.codec decode:data]; XCTAssertNotNil(outputList); XCTAssertEqualObjects(outputList[0], mockHostSmallApi.voidVoidError.code); [expectation fulfill]; @@ -96,7 +96,7 @@ - (void)testAsyncFlutter2HostVoidVoidError { - (void)testAsyncFlutter2Host { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTGetCoreTestsCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; NSString *value = @"Test"; mockHostSmallApi.output = value; @@ -107,7 +107,7 @@ - (void)testAsyncFlutter2Host { NSData *inputEncoded = [binaryMessenger.codec encode:@[ value ]]; XCTestExpectation *expectation = [self expectationWithDescription:@"echo callback"]; binaryMessenger.handlers[channelName](inputEncoded, ^(NSData *data) { - NSArray *outputList = [binaryMessenger.codec decode:data]; + NSArray *outputList = [binaryMessenger.codec decode:data]; NSString *output = outputList[0]; XCTAssertEqualObjects(output, value); [expectation fulfill]; @@ -117,7 +117,7 @@ - (void)testAsyncFlutter2Host { - (void)testAsyncFlutter2HostError { MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:FLTHostSmallApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:FLTGetCoreTestsCodec()]; MockHostSmallApi *mockHostSmallApi = [[MockHostSmallApi alloc] init]; SetUpFLTHostSmallApi(binaryMessenger, mockHostSmallApi); NSString *channelName = @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo"; @@ -126,7 +126,7 @@ - (void)testAsyncFlutter2HostError { NSData *inputEncoded = [binaryMessenger.codec encode:@[ @"Test" ]]; XCTestExpectation *expectation = [self expectationWithDescription:@"echo callback"]; binaryMessenger.handlers[channelName](inputEncoded, ^(NSData *data) { - NSArray *outputList = [binaryMessenger.codec decode:data]; + NSArray *outputList = [binaryMessenger.codec decode:data]; XCTAssertNotNil(outputList); XCTAssertEqualObjects(outputList[0], @"hey"); XCTAssertEqualObjects(outputList[1], @"ho"); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EchoMessenger.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EchoMessenger.m index 95db64e8cee..fe925b1729d 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EchoMessenger.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EchoMessenger.m @@ -29,7 +29,7 @@ - (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)mes - (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message binaryReply:(FlutterBinaryReply _Nullable)callback { - NSArray *args = [self.codec decode:message]; + NSArray *args = [self.codec decode:message]; callback([self.codec encode:args]); } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EnumTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EnumTest.m index 7a3350ee9e0..a9d14641011 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EnumTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/EnumTest.m @@ -21,7 +21,7 @@ - (void)testEcho { PGNEnumStateBox *stateBox = [[PGNEnumStateBox alloc] initWithValue:PGNEnumStateError]; data.state = stateBox; EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PGNEnumApi2HostGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:PGNGetEnumCodec()]; PGNEnumApi2Flutter *api = [[PGNEnumApi2Flutter alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [api echoData:data diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.h index e05042ea27e..99bda6b508e 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.h @@ -7,7 +7,7 @@ NS_ASSUME_NONNULL_BEGIN -typedef id _Nullable (^HandlerBinaryMessengerHandler)(NSArray *_Nonnull args); +typedef id _Nullable (^HandlerBinaryMessengerHandler)(NSArray *_Nonnull args); /// A FlutterBinaryMessenger that calls a supplied method when a call is /// invoked. diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.m index 05c38f82dfe..281bfa5976a 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/HandlerBinaryMessenger.m @@ -32,7 +32,7 @@ - (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)mes - (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message binaryReply:(FlutterBinaryReply _Nullable)callback { - NSArray *args = [self.codec decode:message]; + NSArray *args = [self.codec decode:message]; id result = self.handler(args); callback([self.codec encode:result]); } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m index 54bea7f0e78..882f3c6bedf 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/ListTest.m @@ -22,7 +22,7 @@ - (void)testListInList { inside.testList = @[ @1, @2, @3 ]; top.testList = @[ inside ]; EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:FLTFlutterSmallApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:FLTGetCoreTestsCodec()]; FLTFlutterSmallApi *api = [[FLTFlutterSmallApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [api echoWrappedList:top 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 6e0541554fb..5f82547f308 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 @@ -18,8 +18,8 @@ @implementation MultipleAritytest - (void)testSimple { HandlerBinaryMessenger *binaryMessenger = [[HandlerBinaryMessenger alloc] - initWithCodec:MultipleArityHostApiGetCodec() - handler:^id _Nullable(NSArray *_Nonnull args) { + initWithCodec:GetMultipleArityCodec() + handler:^id _Nullable(NSArray *_Nonnull args) { return @[ @([args[0] intValue] - [args[1] intValue]) ]; }]; MultipleArityFlutterApi *api = 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 e4032dc4cb5..d4dacda1c75 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 @@ -11,14 +11,14 @@ /////////////////////////////////////////////////////////////////////////////////////////// @interface NullFieldsSearchRequest () -+ (NullFieldsSearchRequest *)fromList:(NSArray *)list; -- (NSArray *)toList; ++ (NullFieldsSearchRequest *)fromList:(NSArray *)list; +- (NSArray *)toList; @end /////////////////////////////////////////////////////////////////////////////////////////// @interface NullFieldsSearchReply () -+ (NullFieldsSearchReply *)fromList:(NSArray *)list; -- (NSArray *)toList; ++ (NullFieldsSearchReply *)fromList:(NSArray *)list; +- (NSArray *)toList; @end /////////////////////////////////////////////////////////////////////////////////////////// @@ -39,7 +39,7 @@ - (void)testMakeWithValues { request:request type:typeWrapper]; - NSArray *indices = @[ @1, @2, @3 ]; + NSArray *indices = @[ @1, @2, @3 ]; XCTAssertEqualObjects(@"result", reply.result); XCTAssertEqualObjects(@"error", reply.error); XCTAssertEqualObjects(indices, reply.indices); @@ -66,7 +66,7 @@ - (void)testMakeReplyWithNulls { } - (void)testRequestFromListWithValues { - NSArray *list = @[ + NSArray *list = @[ @"hello", @1, ]; @@ -75,7 +75,7 @@ - (void)testRequestFromListWithValues { } - (void)testRequestFromListWithNulls { - NSArray *list = @[ + NSArray *list = @[ [NSNull null], @1, ]; @@ -84,19 +84,24 @@ - (void)testRequestFromListWithNulls { } - (void)testReplyFromListWithValues { - NSArray *list = @[ + NullFieldsSearchReplyTypeBox *typeWrapper = + [[NullFieldsSearchReplyTypeBox alloc] initWithValue:NullFieldsSearchReplyTypeSuccess]; + + NSArray *listRequest = @[ + @"hello", + @1, + ]; + NSArray *listReply = @[ @"result", @"error", @[ @1, @2, @3 ], - @[ - @"hello", - @1, - ], - @0, + [NullFieldsSearchRequest fromList:listRequest], + typeWrapper, ]; - NSArray *indices = @[ @1, @2, @3 ]; - NullFieldsSearchReply *reply = [NullFieldsSearchReply fromList:list]; + NSArray *indices = @[ @1, @2, @3 ]; + NullFieldsSearchReply *reply = [NullFieldsSearchReply fromList:listReply]; + XCTAssertEqualObjects(@"result", reply.result); XCTAssertEqualObjects(@"error", reply.error); XCTAssertEqualObjects(indices, reply.indices); @@ -105,7 +110,7 @@ - (void)testReplyFromListWithValues { } - (void)testReplyFromListWithNulls { - NSArray *list = @[ + NSArray *list = @[ [NSNull null], [NSNull null], [NSNull null], @@ -122,13 +127,13 @@ - (void)testReplyFromListWithNulls { - (void)testRequestToListWithValuess { NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:@"hello" identifier:1]; - NSArray *list = [request toList]; + NSArray *list = [request toList]; XCTAssertEqual(@"hello", list[0]); } - (void)testRequestToListWithNulls { NullFieldsSearchRequest *request = [NullFieldsSearchRequest makeWithQuery:nil identifier:1]; - NSArray *list = [request toList]; + NSArray *list = [request toList]; XCTAssertEqual([NSNull null], list[0]); } @@ -141,15 +146,13 @@ - (void)testReplyToListWithValuess { indices:@[ @1, @2, @3 ] request:[NullFieldsSearchRequest makeWithQuery:@"hello" identifier:1] type:typeWrapper]; - NSArray *list = [reply toList]; - NSArray *indices = @[ @1, @2, @3 ]; + NSArray *list = [reply toList]; + NSArray *indices = @[ @1, @2, @3 ]; XCTAssertEqualObjects(@"result", list[0]); XCTAssertEqualObjects(@"error", list[1]); XCTAssertEqualObjects(indices, list[2]); - XCTAssertEqualObjects(@"hello", list[3][0]); - NSNumber *typeNumber = list[4]; - NullFieldsSearchReplyTypeBox *output = - [[NullFieldsSearchReplyTypeBox alloc] initWithValue:[typeNumber integerValue]]; + XCTAssertEqualObjects(@"hello", ((NullFieldsSearchRequest *)list[3]).query); + NullFieldsSearchReplyTypeBox *output = list[4]; XCTAssertEqual(typeWrapper.value, output.value); } @@ -160,7 +163,7 @@ - (void)testReplyToListWithNulls { indices:nil request:nil type:nil]; - NSArray *list = [reply toList]; + NSArray *list = [reply toList]; XCTAssertEqual([NSNull null], list[0]); XCTAssertEqual([NSNull null], list[1]); XCTAssertEqual([NSNull null], list[2]); diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullableReturnsTest.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullableReturnsTest.m index 3cd45e6fbc4..54ee040313d 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullableReturnsTest.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/NullableReturnsTest.m @@ -35,7 +35,7 @@ @implementation NullableReturnsTest - (void)testNullableParameterWithFlutterApi { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:NullableArgHostApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetNullableReturnsCodec()]; NullableArgFlutterApi *api = [[NullableArgFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; @@ -50,12 +50,12 @@ - (void)testNullableParameterWithFlutterApi { - (void)testNullableParameterWithHostApi { MockNullableArgHostApi *api = [[MockNullableArgHostApi alloc] init]; MockBinaryMessenger *binaryMessenger = - [[MockBinaryMessenger alloc] initWithCodec:NullableArgHostApiGetCodec()]; + [[MockBinaryMessenger alloc] initWithCodec:GetNullableReturnsCodec()]; NSString *channel = @"dev.flutter.pigeon.pigeon_integration_tests.NullableArgHostApi.doit"; SetUpNullableArgHostApi(binaryMessenger, api); XCTAssertNotNil(binaryMessenger.handlers[channel]); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - NSData *arguments = [NullableArgHostApiGetCodec() encode:@[ [NSNull null] ]]; + NSData *arguments = [GetNullableReturnsCodec() encode:@[ [NSNull null] ]]; binaryMessenger.handlers[channel](arguments, ^(NSData *data) { [expectation fulfill]; }); 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 2c6b7af178f..380fc9bdc6c 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 @@ -18,7 +18,7 @@ @implementation PrimitiveTest - (void)testIntPrimitive { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetPrimitiveCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [api anIntValue:1 @@ -31,7 +31,7 @@ - (void)testIntPrimitive { - (void)testBoolPrimitive { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetPrimitiveCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; BOOL arg = YES; @@ -46,7 +46,7 @@ - (void)testBoolPrimitive { - (void)testDoublePrimitive { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetPrimitiveCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; NSInteger arg = 1.5; @@ -61,7 +61,7 @@ - (void)testDoublePrimitive { - (void)testStringPrimitive { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetPrimitiveCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; NSString *arg = @"hello"; @@ -75,12 +75,12 @@ - (void)testStringPrimitive { - (void)testListPrimitive { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetPrimitiveCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - NSArray *arg = @[ @"hello" ]; + NSArray *arg = @[ @"hello" ]; [api aListValue:arg - completion:^(NSArray *_Nonnull result, FlutterError *_Nullable err) { + completion:^(NSArray *_Nonnull result, FlutterError *_Nullable err) { XCTAssertEqualObjects(arg, result); [expectation fulfill]; }]; @@ -89,7 +89,7 @@ - (void)testListPrimitive { - (void)testMapPrimitive { EchoBinaryMessenger *binaryMessenger = - [[EchoBinaryMessenger alloc] initWithCodec:PrimitiveFlutterApiGetCodec()]; + [[EchoBinaryMessenger alloc] initWithCodec:GetPrimitiveCodec()]; PrimitiveFlutterApi *api = [[PrimitiveFlutterApi alloc] initWithBinaryMessenger:binaryMessenger]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; NSDictionary *arg = @{@"hello" : @1}; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/RunnerTests.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/RunnerTests.m index 60220b7a859..a81c169d003 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/RunnerTests.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/RunnerTests/RunnerTests.m @@ -7,8 +7,8 @@ @import alternate_language_test_plugin; @interface ACMessageSearchReply () -+ (ACMessageSearchReply *)fromList:(NSArray *)list; -- (NSArray *)toList; ++ (ACMessageSearchReply *)fromList:(NSArray *)list; +- (NSArray *)toList; @end @interface RunnerTests : XCTestCase @@ -20,7 +20,7 @@ @implementation RunnerTests - (void)testToMapAndBack { ACMessageSearchReply *reply = [[ACMessageSearchReply alloc] init]; reply.result = @"foobar"; - NSArray *list = [reply toList]; + NSArray *list = [reply toList]; ACMessageSearchReply *copy = [ACMessageSearchReply fromList:list]; XCTAssertEqual(reply.result, copy.result); } @@ -28,7 +28,7 @@ - (void)testToMapAndBack { - (void)testHandlesNull { ACMessageSearchReply *reply = [[ACMessageSearchReply alloc] init]; reply.result = nil; - NSArray *list = [reply toList]; + NSArray *list = [reply toList]; ACMessageSearchReply *copy = [ACMessageSearchReply fromList:list]; XCTAssertNil(copy.result); } @@ -36,7 +36,7 @@ - (void)testHandlesNull { - (void)testHandlesNullFirst { ACMessageSearchReply *reply = [[ACMessageSearchReply alloc] init]; reply.error = @"foobar"; - NSArray *list = [reply toList]; + NSArray *list = [reply toList]; ACMessageSearchReply *copy = [ACMessageSearchReply fromList:list]; XCTAssertEqual(reply.error, copy.error); } diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/DebugProfile.entitlements b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/DebugProfile.entitlements +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/Release.entitlements b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/Release.entitlements +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/pubspec.yaml b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/pubspec.yaml index 68c11e25507..a080fec9236 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/pubspec.yaml +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Pigeon test harness for alternate plugin languages. publish_to: 'none' environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: alternate_language_test_plugin: diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m index e3f26dcb8d7..4ea9519033c 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m @@ -90,9 +90,9 @@ - (nullable id)echoObject:(id)anObject error:(FlutterError *_Nullable *_Nonnull) return anObject; } -- (nullable NSArray *)echoList:(NSArray *)aList +- (nullable NSArray *)echoList:(NSArray *)list error:(FlutterError *_Nullable *_Nonnull)error { - return aList; + return list; } - (nullable NSDictionary *)echoMap:(NSDictionary *)aMap @@ -293,9 +293,9 @@ - (void)echoAsyncObject:(id)anObject completion(anObject, nil); } -- (void)echoAsyncList:(NSArray *)aList +- (void)echoAsyncList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - completion(aList, nil); + completion(list, nil); } - (void)echoAsyncMap:(NSDictionary *)aMap @@ -340,10 +340,10 @@ - (void)echoAsyncNullableObject:(nullable id)anObject completion(anObject, nil); } -- (void)echoAsyncNullableList:(nullable NSArray *)aList +- (void)echoAsyncNullableList:(nullable NSArray *)list completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - completion(aList, nil); + completion(list, nil); } - (void)echoAsyncNullableMap:(nullable NSDictionary *)aMap @@ -451,18 +451,18 @@ - (void)callFlutterEchoString:(NSString *)aString }]; } -- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)aList +- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoUint8List:aList + [self.flutterAPI echoUint8List:list completion:^(FlutterStandardTypedData *value, FlutterError *error) { completion(value, error); }]; } -- (void)callFlutterEchoList:(NSArray *)aList +- (void)callFlutterEchoList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoList:aList + [self.flutterAPI echoList:list completion:^(NSArray *value, FlutterError *error) { completion(value, error); }]; @@ -544,19 +544,19 @@ - (void)callFlutterEchoNullableString:(nullable NSString *)aString }]; } -- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)aList +- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoNullableUint8List:aList + [self.flutterAPI echoNullableUint8List:list completion:^(FlutterStandardTypedData *value, FlutterError *error) { completion(value, error); }]; } -- (void)callFlutterEchoNullableList:(nullable NSArray *)aList +- (void)callFlutterEchoNullableList:(nullable NSArray *)list completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoNullableList:aList + [self.flutterAPI echoNullableList:list completion:^(NSArray *value, FlutterError *error) { completion(value, error); }]; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h index 8e44380d789..2bf7bbf26be 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h @@ -46,11 +46,15 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray aFloatArray:(FlutterStandardTypedData *)aFloatArray - aList:(NSArray *)aList - aMap:(NSDictionary *)aMap anEnum:(FLTAnEnum)anEnum aString:(NSString *)aString - anObject:(id)anObject; + anObject:(id)anObject + list:(NSArray *)list + stringList:(NSArray *)stringList + intList:(NSArray *)intList + doubleList:(NSArray *)doubleList + boolList:(NSArray *)boolList + map:(NSDictionary *)map; @property(nonatomic, assign) BOOL aBool; @property(nonatomic, assign) NSInteger anInt; @property(nonatomic, assign) NSInteger anInt64; @@ -59,11 +63,15 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { @property(nonatomic, strong) FlutterStandardTypedData *a4ByteArray; @property(nonatomic, strong) FlutterStandardTypedData *a8ByteArray; @property(nonatomic, strong) FlutterStandardTypedData *aFloatArray; -@property(nonatomic, copy) NSArray *aList; -@property(nonatomic, copy) NSDictionary *aMap; @property(nonatomic, assign) FLTAnEnum anEnum; @property(nonatomic, copy) NSString *aString; @property(nonatomic, strong) id anObject; +@property(nonatomic, copy) NSArray *list; +@property(nonatomic, copy) NSArray *stringList; +@property(nonatomic, copy) NSArray *intList; +@property(nonatomic, copy) NSArray *doubleList; +@property(nonatomic, copy) NSArray *boolList; +@property(nonatomic, copy) NSDictionary *map; @end /// A class containing all supported nullable types. @@ -76,8 +84,6 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations @@ -85,7 +91,14 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString aNullableObject:(nullable id)aNullableObject - allNullableTypes:(nullable FLTAllNullableTypes *)allNullableTypes; + allNullableTypes:(nullable FLTAllNullableTypes *)allNullableTypes + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + nestedClassList:(nullable NSArray *)nestedClassList + map:(nullable NSDictionary *)map; @property(nonatomic, strong, nullable) NSNumber *aNullableBool; @property(nonatomic, strong, nullable) NSNumber *aNullableInt; @property(nonatomic, strong, nullable) NSNumber *aNullableInt64; @@ -94,8 +107,6 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable4ByteArray; @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable8ByteArray; @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableFloatArray; -@property(nonatomic, copy, nullable) NSArray *aNullableList; -@property(nonatomic, copy, nullable) NSDictionary *aNullableMap; @property(nonatomic, copy, nullable) NSArray *> *nullableNestedList; @property(nonatomic, copy, nullable) NSDictionary *nullableMapWithAnnotations; @@ -104,6 +115,13 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; @property(nonatomic, strong, nullable) FLTAllNullableTypes *allNullableTypes; +@property(nonatomic, copy, nullable) NSArray *list; +@property(nonatomic, copy, nullable) NSArray *stringList; +@property(nonatomic, copy, nullable) NSArray *intList; +@property(nonatomic, copy, nullable) NSArray *doubleList; +@property(nonatomic, copy, nullable) NSArray *boolList; +@property(nonatomic, copy, nullable) NSArray *nestedClassList; +@property(nonatomic, copy, nullable) NSDictionary *map; @end /// The primary purpose for this class is to ensure coverage of Swift structs @@ -118,15 +136,19 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString - aNullableObject:(nullable id)aNullableObject; + aNullableObject:(nullable id)aNullableObject + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + map:(nullable NSDictionary *)map; @property(nonatomic, strong, nullable) NSNumber *aNullableBool; @property(nonatomic, strong, nullable) NSNumber *aNullableInt; @property(nonatomic, strong, nullable) NSNumber *aNullableInt64; @@ -135,8 +157,6 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable4ByteArray; @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullable8ByteArray; @property(nonatomic, strong, nullable) FlutterStandardTypedData *aNullableFloatArray; -@property(nonatomic, copy, nullable) NSArray *aNullableList; -@property(nonatomic, copy, nullable) NSDictionary *aNullableMap; @property(nonatomic, copy, nullable) NSArray *> *nullableNestedList; @property(nonatomic, copy, nullable) NSDictionary *nullableMapWithAnnotations; @@ -144,6 +164,12 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { @property(nonatomic, strong, nullable) FLTAnEnumBox *aNullableEnum; @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; +@property(nonatomic, copy, nullable) NSArray *list; +@property(nonatomic, copy, nullable) NSArray *stringList; +@property(nonatomic, copy, nullable) NSArray *intList; +@property(nonatomic, copy, nullable) NSArray *doubleList; +@property(nonatomic, copy, nullable) NSArray *boolList; +@property(nonatomic, copy, nullable) NSDictionary *map; @end /// A class for testing nested class handling. @@ -166,12 +192,12 @@ typedef NS_ENUM(NSUInteger, FLTAnEnum) { /// A data class containing a List, used in unit tests. @interface FLTTestMessage : NSObject -+ (instancetype)makeWithTestList:(nullable NSArray *)testList; -@property(nonatomic, copy, nullable) NSArray *testList; ++ (instancetype)makeWithTestList:(nullable NSArray *)testList; +@property(nonatomic, copy, nullable) NSArray *testList; @end -/// The codec used by FLTHostIntegrationCoreApi. -NSObject *FLTHostIntegrationCoreApiGetCodec(void); +/// The codec used by all APIs. +NSObject *FLTGetCoreTestsCodec(void); /// The core interface that each host language plugin must implement in /// platform_test integration tests. @@ -219,7 +245,7 @@ NSObject *FLTHostIntegrationCoreApiGetCodec(void); /// Returns the passed list, to test serialization and deserialization. /// /// @return `nil` only when `error != nil`. -- (nullable NSArray *)echoList:(NSArray *)aList +- (nullable NSArray *)echoList:(NSArray *)list error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed map, to test serialization and deserialization. /// @@ -342,7 +368,7 @@ NSObject *FLTHostIntegrationCoreApiGetCodec(void); - (void)echoAsyncObject:(id)anObject completion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test asynchronous serialization and deserialization. -- (void)echoAsyncList:(NSArray *)aList +- (void)echoAsyncList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test asynchronous serialization and deserialization. - (void)echoAsyncMap:(NSDictionary *)aMap @@ -392,7 +418,7 @@ NSObject *FLTHostIntegrationCoreApiGetCodec(void); - (void)echoAsyncNullableObject:(nullable id)anObject completion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test asynchronous serialization and deserialization. -- (void)echoAsyncNullableList:(nullable NSArray *)aList +- (void)echoAsyncNullableList:(nullable NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test asynchronous serialization and deserialization. - (void)echoAsyncNullableMap:(nullable NSDictionary *)aMap @@ -440,10 +466,10 @@ NSObject *FLTHostIntegrationCoreApiGetCodec(void); completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)aList +- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoList:(NSArray *)aList +- (void)callFlutterEchoList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoMap:(NSDictionary *)aMap completion:(void (^)(NSDictionary *_Nullable, @@ -462,10 +488,10 @@ NSObject *FLTHostIntegrationCoreApiGetCodec(void); - (void)callFlutterEchoNullableString:(nullable NSString *)aString completion: (void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)aList +- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoNullableList:(nullable NSArray *)aList +- (void)callFlutterEchoNullableList:(nullable NSArray *)list completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoNullableMap:(nullable NSDictionary *)aMap @@ -486,9 +512,6 @@ extern void SetUpFLTHostIntegrationCoreApiWithSuffix( id binaryMessenger, NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by FLTFlutterIntegrationCoreApi. -NSObject *FLTFlutterIntegrationCoreApiGetCodec(void); - /// The core interface that the Dart platform_test code implements for host /// integration tests to call into. @interface FLTFlutterIntegrationCoreApi : NSObject @@ -545,11 +568,11 @@ NSObject *FLTFlutterIntegrationCoreApiGetCodec(void); - (void)echoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed byte list, to test serialization and deserialization. -- (void)echoUint8List:(FlutterStandardTypedData *)aList +- (void)echoUint8List:(FlutterStandardTypedData *)list completion: (void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test serialization and deserialization. -- (void)echoList:(NSArray *)aList +- (void)echoList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test serialization and deserialization. - (void)echoMap:(NSDictionary *)aMap @@ -571,11 +594,11 @@ NSObject *FLTFlutterIntegrationCoreApiGetCodec(void); - (void)echoNullableString:(nullable NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed byte list, to test serialization and deserialization. -- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)aList +- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test serialization and deserialization. -- (void)echoNullableList:(nullable NSArray *)aList +- (void)echoNullableList:(nullable NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test serialization and deserialization. - (void)echoNullableMap:(nullable NSDictionary *)aMap @@ -592,9 +615,6 @@ NSObject *FLTFlutterIntegrationCoreApiGetCodec(void); completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @end -/// The codec used by FLTHostTrivialApi. -NSObject *FLTHostTrivialApiGetCodec(void); - /// An API that can be implemented for minimal, compile-only tests. @protocol FLTHostTrivialApi - (void)noopWithError:(FlutterError *_Nullable *_Nonnull)error; @@ -607,9 +627,6 @@ extern void SetUpFLTHostTrivialApiWithSuffix(id binaryMe NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by FLTHostSmallApi. -NSObject *FLTHostSmallApiGetCodec(void); - /// A simple API implemented in some unit tests. @protocol FLTHostSmallApi - (void)echoString:(NSString *)aString @@ -624,9 +641,6 @@ extern void SetUpFLTHostSmallApiWithSuffix(id binaryMess NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by FLTFlutterSmallApi. -NSObject *FLTFlutterSmallApiGetCodec(void); - /// A simple API called in some unit tests. @interface FLTFlutterSmallApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; 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 eecb44f2eaf..6dae6ff2abd 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 @@ -17,7 +17,7 @@ #error File requires ARC to be enabled. #endif -static NSArray *wrapResult(id result, FlutterError *error) { +static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] @@ -35,7 +35,7 @@ details:@""]; } -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @@ -51,33 +51,33 @@ - (instancetype)initWithValue:(FLTAnEnum)value { @end @interface FLTAllTypes () -+ (FLTAllTypes *)fromList:(NSArray *)list; -+ (nullable FLTAllTypes *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (FLTAllTypes *)fromList:(NSArray *)list; ++ (nullable FLTAllTypes *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface FLTAllNullableTypes () -+ (FLTAllNullableTypes *)fromList:(NSArray *)list; -+ (nullable FLTAllNullableTypes *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (FLTAllNullableTypes *)fromList:(NSArray *)list; ++ (nullable FLTAllNullableTypes *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface FLTAllNullableTypesWithoutRecursion () -+ (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list; -+ (nullable FLTAllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list; ++ (nullable FLTAllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface FLTAllClassesWrapper () -+ (FLTAllClassesWrapper *)fromList:(NSArray *)list; -+ (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (FLTAllClassesWrapper *)fromList:(NSArray *)list; ++ (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface FLTTestMessage () -+ (FLTTestMessage *)fromList:(NSArray *)list; -+ (nullable FLTTestMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (FLTTestMessage *)fromList:(NSArray *)list; ++ (nullable FLTTestMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @implementation FLTAllTypes @@ -89,11 +89,15 @@ + (instancetype)makeWithABool:(BOOL)aBool a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray aFloatArray:(FlutterStandardTypedData *)aFloatArray - aList:(NSArray *)aList - aMap:(NSDictionary *)aMap anEnum:(FLTAnEnum)anEnum aString:(NSString *)aString - anObject:(id)anObject { + anObject:(id)anObject + list:(NSArray *)list + stringList:(NSArray *)stringList + intList:(NSArray *)intList + doubleList:(NSArray *)doubleList + boolList:(NSArray *)boolList + map:(NSDictionary *)map { FLTAllTypes *pigeonResult = [[FLTAllTypes alloc] init]; pigeonResult.aBool = aBool; pigeonResult.anInt = anInt; @@ -103,14 +107,18 @@ + (instancetype)makeWithABool:(BOOL)aBool pigeonResult.a4ByteArray = a4ByteArray; pigeonResult.a8ByteArray = a8ByteArray; pigeonResult.aFloatArray = aFloatArray; - pigeonResult.aList = aList; - pigeonResult.aMap = aMap; pigeonResult.anEnum = anEnum; pigeonResult.aString = aString; pigeonResult.anObject = anObject; + pigeonResult.list = list; + pigeonResult.stringList = stringList; + pigeonResult.intList = intList; + pigeonResult.doubleList = doubleList; + pigeonResult.boolList = boolList; + pigeonResult.map = map; return pigeonResult; } -+ (FLTAllTypes *)fromList:(NSArray *)list { ++ (FLTAllTypes *)fromList:(NSArray *)list { FLTAllTypes *pigeonResult = [[FLTAllTypes alloc] init]; pigeonResult.aBool = [GetNullableObjectAtIndex(list, 0) boolValue]; pigeonResult.anInt = [GetNullableObjectAtIndex(list, 1) integerValue]; @@ -120,17 +128,22 @@ + (FLTAllTypes *)fromList:(NSArray *)list { pigeonResult.a4ByteArray = GetNullableObjectAtIndex(list, 5); pigeonResult.a8ByteArray = GetNullableObjectAtIndex(list, 6); pigeonResult.aFloatArray = GetNullableObjectAtIndex(list, 7); - pigeonResult.aList = GetNullableObjectAtIndex(list, 8); - pigeonResult.aMap = GetNullableObjectAtIndex(list, 9); - pigeonResult.anEnum = [GetNullableObjectAtIndex(list, 10) integerValue]; - pigeonResult.aString = GetNullableObjectAtIndex(list, 11); - pigeonResult.anObject = GetNullableObjectAtIndex(list, 12); + FLTAnEnumBox *enumBox = GetNullableObjectAtIndex(list, 8); + pigeonResult.anEnum = enumBox.value; + pigeonResult.aString = GetNullableObjectAtIndex(list, 9); + pigeonResult.anObject = GetNullableObjectAtIndex(list, 10); + pigeonResult.list = GetNullableObjectAtIndex(list, 11); + pigeonResult.stringList = GetNullableObjectAtIndex(list, 12); + pigeonResult.intList = GetNullableObjectAtIndex(list, 13); + pigeonResult.doubleList = GetNullableObjectAtIndex(list, 14); + pigeonResult.boolList = GetNullableObjectAtIndex(list, 15); + pigeonResult.map = GetNullableObjectAtIndex(list, 16); return pigeonResult; } -+ (nullable FLTAllTypes *)nullableFromList:(NSArray *)list { ++ (nullable FLTAllTypes *)nullableFromList:(NSArray *)list { return (list) ? [FLTAllTypes fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ @(self.aBool), @(self.anInt), @@ -140,11 +153,15 @@ - (NSArray *)toList { self.a4ByteArray ?: [NSNull null], self.a8ByteArray ?: [NSNull null], self.aFloatArray ?: [NSNull null], - self.aList ?: [NSNull null], - self.aMap ?: [NSNull null], - @(self.anEnum), + [[FLTAnEnumBox alloc] initWithValue:self.anEnum], self.aString ?: [NSNull null], self.anObject ?: [NSNull null], + self.list ?: [NSNull null], + self.stringList ?: [NSNull null], + self.intList ?: [NSNull null], + self.doubleList ?: [NSNull null], + self.boolList ?: [NSNull null], + self.map ?: [NSNull null], ]; } @end @@ -158,8 +175,6 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations @@ -167,7 +182,14 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString aNullableObject:(nullable id)aNullableObject - allNullableTypes:(nullable FLTAllNullableTypes *)allNullableTypes { + allNullableTypes:(nullable FLTAllNullableTypes *)allNullableTypes + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + nestedClassList:(nullable NSArray *)nestedClassList + map:(nullable NSDictionary *)map { FLTAllNullableTypes *pigeonResult = [[FLTAllNullableTypes alloc] init]; pigeonResult.aNullableBool = aNullableBool; pigeonResult.aNullableInt = aNullableInt; @@ -177,8 +199,6 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullable4ByteArray = aNullable4ByteArray; pigeonResult.aNullable8ByteArray = aNullable8ByteArray; pigeonResult.aNullableFloatArray = aNullableFloatArray; - pigeonResult.aNullableList = aNullableList; - pigeonResult.aNullableMap = aNullableMap; pigeonResult.nullableNestedList = nullableNestedList; pigeonResult.nullableMapWithAnnotations = nullableMapWithAnnotations; pigeonResult.nullableMapWithObject = nullableMapWithObject; @@ -186,9 +206,16 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullableString = aNullableString; pigeonResult.aNullableObject = aNullableObject; pigeonResult.allNullableTypes = allNullableTypes; + pigeonResult.list = list; + pigeonResult.stringList = stringList; + pigeonResult.intList = intList; + pigeonResult.doubleList = doubleList; + pigeonResult.boolList = boolList; + pigeonResult.nestedClassList = nestedClassList; + pigeonResult.map = map; return pigeonResult; } -+ (FLTAllNullableTypes *)fromList:(NSArray *)list { ++ (FLTAllNullableTypes *)fromList:(NSArray *)list { FLTAllNullableTypes *pigeonResult = [[FLTAllNullableTypes alloc] init]; pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); pigeonResult.aNullableInt = GetNullableObjectAtIndex(list, 1); @@ -198,27 +225,26 @@ + (FLTAllNullableTypes *)fromList:(NSArray *)list { pigeonResult.aNullable4ByteArray = GetNullableObjectAtIndex(list, 5); pigeonResult.aNullable8ByteArray = GetNullableObjectAtIndex(list, 6); pigeonResult.aNullableFloatArray = GetNullableObjectAtIndex(list, 7); - pigeonResult.aNullableList = GetNullableObjectAtIndex(list, 8); - pigeonResult.aNullableMap = GetNullableObjectAtIndex(list, 9); - pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 10); - pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); - pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); - NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); - FLTAnEnumBox *aNullableEnum = - aNullableEnumAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; - pigeonResult.aNullableEnum = aNullableEnum; - pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); - pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); - pigeonResult.allNullableTypes = - [FLTAllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 16))]; + pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 8); + pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 9); + pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 10); + pigeonResult.aNullableEnum = GetNullableObjectAtIndex(list, 11); + pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 12); + pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 13); + pigeonResult.allNullableTypes = GetNullableObjectAtIndex(list, 14); + pigeonResult.list = GetNullableObjectAtIndex(list, 15); + pigeonResult.stringList = GetNullableObjectAtIndex(list, 16); + pigeonResult.intList = GetNullableObjectAtIndex(list, 17); + pigeonResult.doubleList = GetNullableObjectAtIndex(list, 18); + pigeonResult.boolList = GetNullableObjectAtIndex(list, 19); + pigeonResult.nestedClassList = GetNullableObjectAtIndex(list, 20); + pigeonResult.map = GetNullableObjectAtIndex(list, 21); return pigeonResult; } -+ (nullable FLTAllNullableTypes *)nullableFromList:(NSArray *)list { ++ (nullable FLTAllNullableTypes *)nullableFromList:(NSArray *)list { return (list) ? [FLTAllNullableTypes fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.aNullableBool ?: [NSNull null], self.aNullableInt ?: [NSNull null], @@ -228,16 +254,20 @@ - (NSArray *)toList { 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.aNullableEnum ?: [NSNull null], self.aNullableString ?: [NSNull null], self.aNullableObject ?: [NSNull null], - (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), + self.allNullableTypes ?: [NSNull null], + self.list ?: [NSNull null], + self.stringList ?: [NSNull null], + self.intList ?: [NSNull null], + self.doubleList ?: [NSNull null], + self.boolList ?: [NSNull null], + self.nestedClassList ?: [NSNull null], + self.map ?: [NSNull null], ]; } @end @@ -251,15 +281,19 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject aNullableEnum:(nullable FLTAnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString - aNullableObject:(nullable id)aNullableObject { + aNullableObject:(nullable id)aNullableObject + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + map:(nullable NSDictionary *)map { FLTAllNullableTypesWithoutRecursion *pigeonResult = [[FLTAllNullableTypesWithoutRecursion alloc] init]; pigeonResult.aNullableBool = aNullableBool; @@ -270,17 +304,21 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullable4ByteArray = aNullable4ByteArray; pigeonResult.aNullable8ByteArray = aNullable8ByteArray; pigeonResult.aNullableFloatArray = aNullableFloatArray; - pigeonResult.aNullableList = aNullableList; - pigeonResult.aNullableMap = aNullableMap; pigeonResult.nullableNestedList = nullableNestedList; pigeonResult.nullableMapWithAnnotations = nullableMapWithAnnotations; pigeonResult.nullableMapWithObject = nullableMapWithObject; pigeonResult.aNullableEnum = aNullableEnum; pigeonResult.aNullableString = aNullableString; pigeonResult.aNullableObject = aNullableObject; + pigeonResult.list = list; + pigeonResult.stringList = stringList; + pigeonResult.intList = intList; + pigeonResult.doubleList = doubleList; + pigeonResult.boolList = boolList; + pigeonResult.map = map; return pigeonResult; } -+ (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { ++ (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { FLTAllNullableTypesWithoutRecursion *pigeonResult = [[FLTAllNullableTypesWithoutRecursion alloc] init]; pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); @@ -291,25 +329,24 @@ + (FLTAllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { pigeonResult.aNullable4ByteArray = GetNullableObjectAtIndex(list, 5); pigeonResult.aNullable8ByteArray = GetNullableObjectAtIndex(list, 6); pigeonResult.aNullableFloatArray = GetNullableObjectAtIndex(list, 7); - pigeonResult.aNullableList = GetNullableObjectAtIndex(list, 8); - pigeonResult.aNullableMap = GetNullableObjectAtIndex(list, 9); - pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 10); - pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); - pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); - NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); - FLTAnEnumBox *aNullableEnum = - aNullableEnumAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; - pigeonResult.aNullableEnum = aNullableEnum; - pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); - pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); + pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 8); + pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 9); + pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 10); + pigeonResult.aNullableEnum = GetNullableObjectAtIndex(list, 11); + pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 12); + pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 13); + pigeonResult.list = GetNullableObjectAtIndex(list, 14); + pigeonResult.stringList = GetNullableObjectAtIndex(list, 15); + pigeonResult.intList = GetNullableObjectAtIndex(list, 16); + pigeonResult.doubleList = GetNullableObjectAtIndex(list, 17); + pigeonResult.boolList = GetNullableObjectAtIndex(list, 18); + pigeonResult.map = GetNullableObjectAtIndex(list, 19); return pigeonResult; } -+ (nullable FLTAllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list { ++ (nullable FLTAllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list { return (list) ? [FLTAllNullableTypesWithoutRecursion fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.aNullableBool ?: [NSNull null], self.aNullableInt ?: [NSNull null], @@ -319,15 +356,18 @@ - (NSArray *)toList { 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.aNullableEnum ?: [NSNull null], self.aNullableString ?: [NSNull null], self.aNullableObject ?: [NSNull null], + self.list ?: [NSNull null], + self.stringList ?: [NSNull null], + self.intList ?: [NSNull null], + self.doubleList ?: [NSNull null], + self.boolList ?: [NSNull null], + self.map ?: [NSNull null], ]; } @end @@ -343,117 +383,122 @@ + (instancetype)makeWithAllNullableTypes:(FLTAllNullableTypes *)allNullableTypes pigeonResult.allTypes = allTypes; return pigeonResult; } -+ (FLTAllClassesWrapper *)fromList:(NSArray *)list { ++ (FLTAllClassesWrapper *)fromList:(NSArray *)list { FLTAllClassesWrapper *pigeonResult = [[FLTAllClassesWrapper alloc] init]; - pigeonResult.allNullableTypes = - [FLTAllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - pigeonResult.allNullableTypesWithoutRecursion = - [FLTAllNullableTypesWithoutRecursion nullableFromList:(GetNullableObjectAtIndex(list, 1))]; - pigeonResult.allTypes = [FLTAllTypes nullableFromList:(GetNullableObjectAtIndex(list, 2))]; + pigeonResult.allNullableTypes = GetNullableObjectAtIndex(list, 0); + pigeonResult.allNullableTypesWithoutRecursion = GetNullableObjectAtIndex(list, 1); + pigeonResult.allTypes = GetNullableObjectAtIndex(list, 2); return pigeonResult; } -+ (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray *)list { ++ (nullable FLTAllClassesWrapper *)nullableFromList:(NSArray *)list { return (list) ? [FLTAllClassesWrapper fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ - (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), - (self.allNullableTypesWithoutRecursion ? [self.allNullableTypesWithoutRecursion toList] - : [NSNull null]), - (self.allTypes ? [self.allTypes toList] : [NSNull null]), + self.allNullableTypes ?: [NSNull null], + self.allNullableTypesWithoutRecursion ?: [NSNull null], + self.allTypes ?: [NSNull null], ]; } @end @implementation FLTTestMessage -+ (instancetype)makeWithTestList:(nullable NSArray *)testList { ++ (instancetype)makeWithTestList:(nullable NSArray *)testList { FLTTestMessage *pigeonResult = [[FLTTestMessage alloc] init]; pigeonResult.testList = testList; return pigeonResult; } -+ (FLTTestMessage *)fromList:(NSArray *)list { ++ (FLTTestMessage *)fromList:(NSArray *)list { FLTTestMessage *pigeonResult = [[FLTTestMessage alloc] init]; pigeonResult.testList = GetNullableObjectAtIndex(list, 0); return pigeonResult; } -+ (nullable FLTTestMessage *)nullableFromList:(NSArray *)list { ++ (nullable FLTTestMessage *)nullableFromList:(NSArray *)list { return (list) ? [FLTTestMessage fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.testList ?: [NSNull null], ]; } @end -@interface FLTHostIntegrationCoreApiCodecReader : FlutterStandardReader +@interface FLTCoreTestsPigeonCodecReader : FlutterStandardReader @end -@implementation FLTHostIntegrationCoreApiCodecReader +@implementation FLTCoreTestsPigeonCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { - case 128: - return [FLTAllClassesWrapper fromList:[self readValue]]; case 129: - return [FLTAllNullableTypes fromList:[self readValue]]; + return [FLTAllTypes fromList:[self readValue]]; case 130: - return [FLTAllNullableTypesWithoutRecursion fromList:[self readValue]]; + return [FLTAllNullableTypes fromList:[self readValue]]; case 131: - return [FLTAllTypes fromList:[self readValue]]; + return [FLTAllNullableTypesWithoutRecursion fromList:[self readValue]]; case 132: + return [FLTAllClassesWrapper fromList:[self readValue]]; + case 133: return [FLTTestMessage fromList:[self readValue]]; + case 134: { + NSNumber *enumAsNumber = [self readValue]; + return enumAsNumber == nil ? nil + : [[FLTAnEnumBox alloc] initWithValue:[enumAsNumber integerValue]]; + } default: return [super readValueOfType:type]; } } @end -@interface FLTHostIntegrationCoreApiCodecWriter : FlutterStandardWriter +@interface FLTCoreTestsPigeonCodecWriter : FlutterStandardWriter @end -@implementation FLTHostIntegrationCoreApiCodecWriter +@implementation FLTCoreTestsPigeonCodecWriter - (void)writeValue:(id)value { - if ([value isKindOfClass:[FLTAllClassesWrapper class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTAllNullableTypes class]]) { + if ([value isKindOfClass:[FLTAllTypes class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTAllNullableTypesWithoutRecursion class]]) { + } else if ([value isKindOfClass:[FLTAllNullableTypes class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTAllTypes class]]) { + } else if ([value isKindOfClass:[FLTAllNullableTypesWithoutRecursion class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTTestMessage class]]) { + } else if ([value isKindOfClass:[FLTAllClassesWrapper class]]) { [self writeByte:132]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FLTTestMessage class]]) { + [self writeByte:133]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FLTAnEnumBox class]]) { + FLTAnEnumBox *box = (FLTAnEnumBox *)value; + [self writeByte:134]; + [self writeValue:(value == nil ? [NSNull null] : [NSNumber numberWithInteger:box.value])]; } else { [super writeValue:value]; } } @end -@interface FLTHostIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter +@interface FLTCoreTestsPigeonCodecReaderWriter : FlutterStandardReaderWriter @end -@implementation FLTHostIntegrationCoreApiCodecReaderWriter +@implementation FLTCoreTestsPigeonCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FLTHostIntegrationCoreApiCodecWriter alloc] initWithData:data]; + return [[FLTCoreTestsPigeonCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FLTHostIntegrationCoreApiCodecReader alloc] initWithData:data]; + return [[FLTCoreTestsPigeonCodecReader alloc] initWithData:data]; } @end -NSObject *FLTHostIntegrationCoreApiGetCodec(void) { +NSObject *FLTGetCoreTestsCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - FLTHostIntegrationCoreApiCodecReaderWriter *readerWriter = - [[FLTHostIntegrationCoreApiCodecReaderWriter alloc] init]; + FLTCoreTestsPigeonCodecReaderWriter *readerWriter = + [[FLTCoreTestsPigeonCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } - void SetUpFLTHostIntegrationCoreApi(id binaryMessenger, NSObject *api) { SetUpFLTHostIntegrationCoreApiWithSuffix(binaryMessenger, api, @""); @@ -474,7 +519,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.noop", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopWithError:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(noopWithError:)", @@ -496,14 +541,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAllTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoAllTypes:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAllTypes:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTAllTypes *output = [api echoAllTypes:arg_everything error:&error]; @@ -521,7 +566,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.throwError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(throwErrorWithError:)], @@ -544,7 +589,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.throwErrorFromVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwErrorFromVoidWithError:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -567,7 +612,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.throwFlutterError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwFlutterErrorWithError:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -590,13 +635,13 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoInt:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api echoInt:arg_anInt error:&error]; @@ -614,14 +659,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoDouble:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; NSNumber *output = [api echoDouble:arg_aDouble error:&error]; @@ -639,13 +684,13 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoBool:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoBool:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; NSNumber *output = [api echoBool:arg_aBool error:&error]; @@ -663,14 +708,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoString:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoString:arg_aString error:&error]; @@ -688,14 +733,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoUint8List:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoUint8List:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aUint8List = GetNullableObjectAtIndex(args, 0); FlutterError *error; FlutterStandardTypedData *output = [api echoUint8List:arg_aUint8List error:&error]; @@ -713,14 +758,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoObject:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoObject:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); FlutterError *error; id output = [api echoObject:arg_anObject error:&error]; @@ -738,16 +783,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoList:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoList:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); FlutterError *error; - NSArray *output = [api echoList:arg_aList error:&error]; + NSArray *output = [api echoList:arg_list error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -762,13 +807,13 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoMap:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoMap:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSDictionary *output = [api echoMap:arg_aMap error:&error]; @@ -786,14 +831,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoClassWrapper", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoClassWrapper:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoClassWrapper:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTAllClassesWrapper *output = [api echoClassWrapper:arg_wrapper error:&error]; @@ -811,17 +856,17 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoEnum:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to @selector(echoEnum:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FLTAnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSArray *args = message; + FLTAnEnumBox *enumBox = GetNullableObjectAtIndex(args, 0); + FLTAnEnum arg_anEnum = enumBox.value; FlutterError *error; - FLTAnEnumBox *enumBox = [api echoEnum:arg_anEnum error:&error]; - NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value]; + FLTAnEnumBox *output = [api echoEnum:arg_anEnum error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -836,14 +881,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNamedDefaultString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNamedDefaultString:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNamedDefaultString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoNamedDefaultString:arg_aString error:&error]; @@ -862,14 +907,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoOptionalDefaultDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoOptionalDefaultDouble:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoOptionalDefaultDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; NSNumber *output = [api echoOptionalDefaultDouble:arg_aDouble error:&error]; @@ -887,14 +932,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoRequiredInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoRequiredInt:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoRequiredInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api echoRequiredInt:arg_anInt error:&error]; @@ -912,14 +957,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAllNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAllNullableTypes:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAllNullableTypes:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTAllNullableTypes *output = [api echoAllNullableTypes:arg_everything error:&error]; @@ -939,14 +984,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAllNullableTypesWithoutRecursion:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAllNullableTypesWithoutRecursion:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTAllNullableTypesWithoutRecursion *output = @@ -967,14 +1012,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.extractNestedNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(extractNestedNullableStringFrom:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(extractNestedNullableStringFrom:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api extractNestedNullableStringFrom:arg_wrapper error:&error]; @@ -994,14 +1039,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.createNestedNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createNestedObjectWithNullableString:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(createNestedObjectWithNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_nullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTAllClassesWrapper *output = [api createNestedObjectWithNullableString:arg_nullableString @@ -1021,7 +1066,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.sendMultipleNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(sendMultipleNullableTypesABool: anInt:aString:error:)], @@ -1029,7 +1074,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"@selector(sendMultipleNullableTypesABool:anInt:aString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -1054,7 +1099,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)], @@ -1062,7 +1107,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"@selector(sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -1086,14 +1131,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableInt:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoNullableInt:arg_aNullableInt error:&error]; @@ -1111,14 +1156,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableDouble:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableDouble = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoNullableDouble:arg_aNullableDouble error:&error]; @@ -1136,14 +1181,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableBool:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableBool:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoNullableBool:arg_aNullableBool error:&error]; @@ -1161,14 +1206,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableString:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoNullableString:arg_aNullableString error:&error]; @@ -1186,14 +1231,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableUint8List:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableUint8List:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aNullableUint8List = GetNullableObjectAtIndex(args, 0); FlutterError *error; FlutterStandardTypedData *output = [api echoNullableUint8List:arg_aNullableUint8List @@ -1212,14 +1257,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableObject:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableObject:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_aNullableObject = GetNullableObjectAtIndex(args, 0); FlutterError *error; id output = [api echoNullableObject:arg_aNullableObject error:&error]; @@ -1237,14 +1282,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableList:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableList:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSArray *arg_aNullableList = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSArray *output = [api echoNullableList:arg_aNullableList error:&error]; @@ -1262,14 +1307,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableMap:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableMap:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aNullableMap = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSDictionary *output = [api echoNullableMap:arg_aNullableMap error:&error]; @@ -1286,22 +1331,17 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNullableEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableEnum:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableEnum:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - FLTAnEnumBox *arg_anEnum = - arg_anEnumAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + NSArray *args = message; + FLTAnEnumBox *arg_anEnum = GetNullableObjectAtIndex(args, 0); FlutterError *error; - FLTAnEnumBox *enumBox = [api echoNullableEnum:arg_anEnum error:&error]; - NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value]; + FLTAnEnumBox *output = [api echoNullableEnum:arg_anEnum error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -1317,14 +1357,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoOptionalNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoOptionalNullableInt:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoOptionalNullableInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoOptionalNullableInt:arg_aNullableInt error:&error]; @@ -1343,14 +1383,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoNamedNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNamedNullableString:error:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNamedNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoNamedNullableString:arg_aNullableString error:&error]; @@ -1369,7 +1409,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.noopAsync", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopAsyncWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -1392,14 +1432,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncInt:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api echoAsyncInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1418,14 +1458,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncDouble:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api echoAsyncDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1444,14 +1484,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncBool:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api echoAsyncBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1470,14 +1510,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncString:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api echoAsyncString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -1496,14 +1536,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncUint8List:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aUint8List = GetNullableObjectAtIndex(args, 0); [api echoAsyncUint8List:arg_aUint8List completion:^(FlutterStandardTypedData *_Nullable output, @@ -1523,14 +1563,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncObject:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncObject:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); [api echoAsyncObject:arg_anObject completion:^(id _Nullable output, FlutterError *_Nullable error) { @@ -1549,16 +1589,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncList:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api echoAsyncList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api echoAsyncList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1575,14 +1615,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncMap:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api echoAsyncMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -1602,19 +1642,18 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncEnum:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FLTAnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSArray *args = message; + FLTAnEnumBox *enumBox = GetNullableObjectAtIndex(args, 0); + FLTAnEnum arg_anEnum = enumBox.value; [api echoAsyncEnum:arg_anEnum - completion:^(FLTAnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; + completion:^(FLTAnEnumBox *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; @@ -1630,7 +1669,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.throwAsyncError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncErrorWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -1654,7 +1693,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.throwAsyncErrorFromVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncErrorFromVoidWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -1677,7 +1716,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.throwAsyncFlutterError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncFlutterErrorWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -1701,14 +1740,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncAllTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncAllTypes:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncAllTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncAllTypes:arg_everything completion:^(FLTAllTypes *_Nullable output, FlutterError *_Nullable error) { @@ -1728,14 +1767,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableAllNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableAllNullableTypes:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableAllNullableTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableAllNullableTypes:arg_everything completion:^(FLTAllNullableTypes *_Nullable output, @@ -1757,7 +1796,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"echoAsyncNullableAllNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)], @@ -1765,7 +1804,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"@selector(echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableAllNullableTypesWithoutRecursion:arg_everything completion:^(FLTAllNullableTypesWithoutRecursion @@ -1786,14 +1825,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableInt:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1813,14 +1852,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableDouble:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1839,14 +1878,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableBool:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1866,14 +1905,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableString:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -1893,14 +1932,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableUint8List:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aUint8List = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableUint8List:arg_aUint8List completion:^(FlutterStandardTypedData *_Nullable output, @@ -1921,14 +1960,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableObject:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableObject:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableObject:arg_anObject completion:^(id _Nullable output, FlutterError *_Nullable error) { @@ -1947,16 +1986,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableList:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api echoAsyncNullableList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api echoAsyncNullableList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1973,14 +2012,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableMap:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -2000,26 +2039,20 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.echoAsyncNullableEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableEnum:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - FLTAnEnumBox *arg_anEnum = - arg_anEnumAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; - [api echoAsyncNullableEnum:arg_anEnum - completion:^(FLTAnEnumBox *_Nullable enumValue, - FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; - callback(wrapResult(output, error)); - }]; + NSArray *args = message; + FLTAnEnumBox *arg_anEnum = GetNullableObjectAtIndex(args, 0); + [api + echoAsyncNullableEnum:arg_anEnum + completion:^(FLTAnEnumBox *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; }]; } else { [channel setMessageHandler:nil]; @@ -2032,7 +2065,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterNoop", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterNoopWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -2054,7 +2087,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterThrowError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterThrowErrorWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -2078,7 +2111,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterThrowErrorFromVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterThrowErrorFromVoidWithCompletion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @@ -2101,14 +2134,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoAllTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoAllTypes:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoAllTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllTypes:arg_everything completion:^(FLTAllTypes *_Nullable output, @@ -2128,14 +2161,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoAllNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoAllNullableTypes:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoAllNullableTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllNullableTypes:arg_everything completion:^(FLTAllNullableTypes *_Nullable output, @@ -2156,7 +2189,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterSendMultipleNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (callFlutterSendMultipleNullableTypesABool:anInt:aString:completion:)], @@ -2164,7 +2197,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"@selector(callFlutterSendMultipleNullableTypesABool:anInt:aString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -2189,7 +2222,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"callFlutterEchoAllNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (callFlutterEchoAllNullableTypesWithoutRecursion:completion:)], @@ -2197,7 +2230,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"@selector(callFlutterEchoAllNullableTypesWithoutRecursion:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FLTAllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllNullableTypesWithoutRecursion:arg_everything completion:^(FLTAllNullableTypesWithoutRecursion @@ -2219,7 +2252,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"callFlutterSendMultipleNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector @@ -2230,7 +2263,7 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -2255,14 +2288,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoBool:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api callFlutterEchoBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -2280,14 +2313,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoInt:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api callFlutterEchoInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -2305,14 +2338,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoDouble:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api callFlutterEchoDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -2330,14 +2363,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoString:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -2356,16 +2389,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoUint8List:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FlutterStandardTypedData *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoUint8List:arg_aList + NSArray *args = message; + FlutterStandardTypedData *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoUint8List:arg_list completion:^(FlutterStandardTypedData *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2382,16 +2415,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoList:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -2407,14 +2440,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoMap:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -2433,20 +2466,18 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoEnum:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FLTAnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSArray *args = message; + FLTAnEnumBox *enumBox = GetNullableObjectAtIndex(args, 0); + FLTAnEnum arg_anEnum = enumBox.value; [api callFlutterEchoEnum:arg_anEnum - completion:^(FLTAnEnumBox *_Nullable enumValue, - FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; + completion:^(FLTAnEnumBox *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; @@ -2462,14 +2493,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableBool:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableBool:arg_aBool completion:^(NSNumber *_Nullable output, @@ -2489,14 +2520,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableInt:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableInt:arg_anInt completion:^(NSNumber *_Nullable output, @@ -2516,14 +2547,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableDouble:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableDouble:arg_aDouble completion:^(NSNumber *_Nullable output, @@ -2543,14 +2574,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableString:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableString:arg_aString completion:^(NSString *_Nullable output, @@ -2570,16 +2601,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableUint8List:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FlutterStandardTypedData *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoNullableUint8List:arg_aList + NSArray *args = message; + FlutterStandardTypedData *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoNullableUint8List:arg_list completion:^(FlutterStandardTypedData *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2597,16 +2628,16 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableList:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoNullableList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoNullableList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2624,14 +2655,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableMap:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -2651,25 +2682,18 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterEchoNullableEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableEnum:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - FLTAnEnumBox *arg_anEnum = - arg_anEnumAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + NSArray *args = message; + FLTAnEnumBox *arg_anEnum = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableEnum:arg_anEnum - completion:^(FLTAnEnumBox *_Nullable enumValue, + completion:^(FLTAnEnumBox *_Nullable output, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil - : [NSNumber numberWithInteger:enumValue.value]; callback(wrapResult(output, error)); }]; }]; @@ -2685,14 +2709,14 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM @"HostIntegrationCoreApi.callFlutterSmallApiEchoString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterSmallApiEchoString:completion:)], @"FLTHostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterSmallApiEchoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api callFlutterSmallApiEchoString:arg_aString completion:^(NSString *_Nullable output, @@ -2705,74 +2729,6 @@ void SetUpFLTHostIntegrationCoreApiWithSuffix(id binaryM } } } -@interface FLTFlutterIntegrationCoreApiCodecReader : FlutterStandardReader -@end -@implementation FLTFlutterIntegrationCoreApiCodecReader -- (nullable id)readValueOfType:(UInt8)type { - switch (type) { - case 128: - return [FLTAllClassesWrapper fromList:[self readValue]]; - case 129: - return [FLTAllNullableTypes fromList:[self readValue]]; - case 130: - return [FLTAllNullableTypesWithoutRecursion fromList:[self readValue]]; - case 131: - return [FLTAllTypes fromList:[self readValue]]; - case 132: - return [FLTTestMessage fromList:[self readValue]]; - default: - return [super readValueOfType:type]; - } -} -@end - -@interface FLTFlutterIntegrationCoreApiCodecWriter : FlutterStandardWriter -@end -@implementation FLTFlutterIntegrationCoreApiCodecWriter -- (void)writeValue:(id)value { - if ([value isKindOfClass:[FLTAllClassesWrapper class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTAllNullableTypes class]]) { - [self writeByte:129]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTAllNullableTypesWithoutRecursion class]]) { - [self writeByte:130]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTAllTypes class]]) { - [self writeByte:131]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FLTTestMessage class]]) { - [self writeByte:132]; - [self writeValue:[value toList]]; - } else { - [super writeValue:value]; - } -} -@end - -@interface FLTFlutterIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation FLTFlutterIntegrationCoreApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FLTFlutterIntegrationCoreApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FLTFlutterIntegrationCoreApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *FLTFlutterIntegrationCoreApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - static dispatch_once_t sPred = 0; - dispatch_once(&sPred, ^{ - FLTFlutterIntegrationCoreApiCodecReaderWriter *readerWriter = - [[FLTFlutterIntegrationCoreApiCodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - @interface FLTFlutterIntegrationCoreApi () @property(nonatomic, strong) NSObject *binaryMessenger; @property(nonatomic, strong) NSString *messageChannelSuffix; @@ -2802,7 +2758,7 @@ - (void)noopWithCompletion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2827,7 +2783,7 @@ - (void)throwErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2853,7 +2809,7 @@ - (void)throwErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))comp FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2879,7 +2835,7 @@ - (void)echoAllTypes:(FLTAllTypes *)arg_everything FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2907,7 +2863,7 @@ - (void)echoAllNullableTypes:(nullable FLTAllNullableTypes *)arg_everything FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2937,7 +2893,7 @@ - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], arg_aNullableString ?: [NSNull null] @@ -2970,7 +2926,7 @@ - (void)echoAllNullableTypesWithoutRecursion: FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3003,7 +2959,7 @@ - (void)sendMultipleNullableTypesWithoutRecursionABool:(nullable NSNumber *)arg_ FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], arg_aNullableString ?: [NSNull null] @@ -3034,7 +2990,7 @@ - (void)echoBool:(BOOL)arg_aBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ @(arg_aBool) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3060,7 +3016,7 @@ - (void)echoInt:(NSInteger)arg_anInt FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ @(arg_anInt) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3087,7 +3043,7 @@ - (void)echoDouble:(double)arg_aDouble FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ @(arg_aDouble) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3114,7 +3070,7 @@ - (void)echoString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3131,7 +3087,7 @@ - (void)echoString:(NSString *)arg_aString } }]; } -- (void)echoUint8List:(FlutterStandardTypedData *)arg_aList +- (void)echoUint8List:(FlutterStandardTypedData *)arg_list completion: (void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = [NSString @@ -3142,8 +3098,8 @@ - (void)echoUint8List:(FlutterStandardTypedData *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:FLTGetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3160,7 +3116,7 @@ - (void)echoUint8List:(FlutterStandardTypedData *)arg_aList } }]; } -- (void)echoList:(NSArray *)arg_aList +- (void)echoList:(NSArray *)arg_list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = [NSString stringWithFormat: @@ -3170,8 +3126,8 @@ - (void)echoList:(NSArray *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:FLTGetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3197,7 +3153,7 @@ - (void)echoMap:(NSDictionary *)arg_aMap FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aMap ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3225,8 +3181,8 @@ - (void)echoEnum:(FLTAnEnum)arg_anEnum FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ [NSNumber numberWithInteger:arg_anEnum] ] + codec:FLTGetCoreTestsCodec()]; + [channel sendMessage:@[ [[FLTAnEnumBox alloc] initWithValue:arg_anEnum] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3234,11 +3190,7 @@ - (void)echoEnum:(FLTAnEnum)arg_anEnum message:reply[1] details:reply[2]]); } else { - NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; - FLTAnEnumBox *output = - outputAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; + FLTAnEnumBox *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -3256,7 +3208,7 @@ - (void)echoNullableBool:(nullable NSNumber *)arg_aBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aBool ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3283,7 +3235,7 @@ - (void)echoNullableInt:(nullable NSNumber *)arg_anInt FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_anInt ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3310,7 +3262,7 @@ - (void)echoNullableDouble:(nullable NSNumber *)arg_aDouble FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aDouble ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3337,7 +3289,7 @@ - (void)echoNullableString:(nullable NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3354,7 +3306,7 @@ - (void)echoNullableString:(nullable NSString *)arg_aString } }]; } -- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList +- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @@ -3365,8 +3317,8 @@ - (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:FLTGetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3383,7 +3335,7 @@ - (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList } }]; } -- (void)echoNullableList:(nullable NSArray *)arg_aList +- (void)echoNullableList:(nullable NSArray *)arg_list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = [NSString stringWithFormat: @@ -3393,8 +3345,8 @@ - (void)echoNullableList:(nullable NSArray *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:FLTGetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3421,7 +3373,7 @@ - (void)echoNullableMap:(nullable NSDictionary *)arg_aMap FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aMap ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3449,9 +3401,8 @@ - (void)echoNullableEnum:(nullable FLTAnEnumBox *)arg_anEnum FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_anEnum == nil ? [NSNull null] - : [NSNumber numberWithInteger:arg_anEnum.value] ] + codec:FLTGetCoreTestsCodec()]; + [channel sendMessage:@[ arg_anEnum == nil ? [NSNull null] : arg_anEnum ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3459,11 +3410,7 @@ - (void)echoNullableEnum:(nullable FLTAnEnumBox *)arg_anEnum message:reply[1] details:reply[2]]); } else { - NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; - FLTAnEnumBox *output = - outputAsNumber == nil - ? nil - : [[FLTAnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; + FLTAnEnumBox *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -3480,7 +3427,7 @@ - (void)noopAsyncWithCompletion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -3506,7 +3453,7 @@ - (void)echoAsyncString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterIntegrationCoreApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3525,12 +3472,6 @@ - (void)echoAsyncString:(NSString *)arg_aString } @end -NSObject *FLTHostTrivialApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - sSharedObject = [FlutterStandardMessageCodec sharedInstance]; - return sSharedObject; -} - void SetUpFLTHostTrivialApi(id binaryMessenger, NSObject *api) { SetUpFLTHostTrivialApiWithSuffix(binaryMessenger, api, @""); @@ -3550,7 +3491,7 @@ void SetUpFLTHostTrivialApiWithSuffix(id binaryMessenger @"dev.flutter.pigeon.pigeon_integration_tests.HostTrivialApi.noop", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostTrivialApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopWithError:)], @"FLTHostTrivialApi api (%@) doesn't respond to @selector(noopWithError:)", api); @@ -3564,12 +3505,6 @@ void SetUpFLTHostTrivialApiWithSuffix(id binaryMessenger } } } -NSObject *FLTHostSmallApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - sSharedObject = [FlutterStandardMessageCodec sharedInstance]; - return sSharedObject; -} - void SetUpFLTHostSmallApi(id binaryMessenger, NSObject *api) { SetUpFLTHostSmallApiWithSuffix(binaryMessenger, api, @""); @@ -3589,13 +3524,13 @@ void SetUpFLTHostSmallApiWithSuffix(id binaryMessenger, @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostSmallApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoString:completion:)], @"FLTHostSmallApi api (%@) doesn't respond to @selector(echoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api echoString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -3613,7 +3548,7 @@ void SetUpFLTHostSmallApiWithSuffix(id binaryMessenger, @"HostSmallApi.voidVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FLTHostSmallApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(voidVoidWithCompletion:)], @"FLTHostSmallApi api (%@) doesn't respond to @selector(voidVoidWithCompletion:)", @@ -3628,54 +3563,6 @@ void SetUpFLTHostSmallApiWithSuffix(id binaryMessenger, } } } -@interface FLTFlutterSmallApiCodecReader : FlutterStandardReader -@end -@implementation FLTFlutterSmallApiCodecReader -- (nullable id)readValueOfType:(UInt8)type { - switch (type) { - case 128: - return [FLTTestMessage fromList:[self readValue]]; - default: - return [super readValueOfType:type]; - } -} -@end - -@interface FLTFlutterSmallApiCodecWriter : FlutterStandardWriter -@end -@implementation FLTFlutterSmallApiCodecWriter -- (void)writeValue:(id)value { - if ([value isKindOfClass:[FLTTestMessage class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else { - [super writeValue:value]; - } -} -@end - -@interface FLTFlutterSmallApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation FLTFlutterSmallApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FLTFlutterSmallApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FLTFlutterSmallApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *FLTFlutterSmallApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - static dispatch_once_t sPred = 0; - dispatch_once(&sPred, ^{ - FLTFlutterSmallApiCodecReaderWriter *readerWriter = - [[FLTFlutterSmallApiCodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - @interface FLTFlutterSmallApi () @property(nonatomic, strong) NSObject *binaryMessenger; @property(nonatomic, strong) NSString *messageChannelSuffix; @@ -3706,7 +3593,7 @@ - (void)echoWrappedList:(FLTTestMessage *)arg_msg FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterSmallApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_msg ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3732,7 +3619,7 @@ - (void)echoString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FLTFlutterSmallApiGetCodec()]; + codec:FLTGetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m index b35ac7907ec..0cdec98b9ed 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 @@ -88,9 +88,9 @@ - (nullable id)echoObject:(id)anObject error:(FlutterError *_Nullable *_Nonnull) return anObject; } -- (nullable NSArray *)echoList:(NSArray *)aList +- (nullable NSArray *)echoList:(NSArray *)list error:(FlutterError *_Nullable *_Nonnull)error { - return aList; + return list; } - (nullable NSDictionary *)echoMap:(NSDictionary *)aMap @@ -288,9 +288,9 @@ - (void)echoAsyncObject:(id)anObject completion(anObject, nil); } -- (void)echoAsyncList:(NSArray *)aList +- (void)echoAsyncList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - completion(aList, nil); + completion(list, nil); } - (void)echoAsyncMap:(NSDictionary *)aMap @@ -335,10 +335,10 @@ - (void)echoAsyncNullableObject:(nullable id)anObject completion(anObject, nil); } -- (void)echoAsyncNullableList:(nullable NSArray *)aList +- (void)echoAsyncNullableList:(nullable NSArray *)list completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - completion(aList, nil); + completion(list, nil); } - (void)echoAsyncNullableMap:(nullable NSDictionary *)aMap @@ -443,18 +443,18 @@ - (void)callFlutterEchoString:(NSString *)aString }]; } -- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)aList +- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoUint8List:aList + [self.flutterAPI echoUint8List:list completion:^(FlutterStandardTypedData *value, FlutterError *error) { completion(value, error); }]; } -- (void)callFlutterEchoList:(NSArray *)aList +- (void)callFlutterEchoList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoList:aList + [self.flutterAPI echoList:list completion:^(NSArray *value, FlutterError *error) { completion(value, error); }]; @@ -535,19 +535,19 @@ - (void)callFlutterEchoNullableString:(nullable NSString *)aString }]; } -- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)aList +- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoNullableUint8List:aList + [self.flutterAPI echoNullableUint8List:list completion:^(FlutterStandardTypedData *value, FlutterError *error) { completion(value, error); }]; } -- (void)callFlutterEchoNullableList:(nullable NSArray *)aList +- (void)callFlutterEchoNullableList:(nullable NSArray *)list completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { - [self.flutterAPI echoNullableList:aList + [self.flutterAPI echoNullableList:list completion:^(NSArray *value, FlutterError *error) { completion(value, 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 4d6fc8de326..f2b2c074755 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 @@ -46,11 +46,15 @@ typedef NS_ENUM(NSUInteger, AnEnum) { a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray aFloatArray:(FlutterStandardTypedData *)aFloatArray - aList:(NSArray *)aList - aMap:(NSDictionary *)aMap anEnum:(AnEnum)anEnum aString:(NSString *)aString - anObject:(id)anObject; + anObject:(id)anObject + list:(NSArray *)list + stringList:(NSArray *)stringList + intList:(NSArray *)intList + doubleList:(NSArray *)doubleList + boolList:(NSArray *)boolList + map:(NSDictionary *)map; @property(nonatomic, assign) BOOL aBool; @property(nonatomic, assign) NSInteger anInt; @property(nonatomic, assign) NSInteger anInt64; @@ -59,11 +63,15 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @property(nonatomic, strong) FlutterStandardTypedData *a4ByteArray; @property(nonatomic, strong) FlutterStandardTypedData *a8ByteArray; @property(nonatomic, strong) FlutterStandardTypedData *aFloatArray; -@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; +@property(nonatomic, copy) NSArray *list; +@property(nonatomic, copy) NSArray *stringList; +@property(nonatomic, copy) NSArray *intList; +@property(nonatomic, copy) NSArray *doubleList; +@property(nonatomic, copy) NSArray *boolList; +@property(nonatomic, copy) NSDictionary *map; @end /// A class containing all supported nullable types. @@ -76,8 +84,6 @@ typedef NS_ENUM(NSUInteger, AnEnum) { aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations @@ -85,7 +91,14 @@ typedef NS_ENUM(NSUInteger, AnEnum) { aNullableEnum:(nullable AnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString aNullableObject:(nullable id)aNullableObject - allNullableTypes:(nullable AllNullableTypes *)allNullableTypes; + allNullableTypes:(nullable AllNullableTypes *)allNullableTypes + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + nestedClassList:(nullable NSArray *)nestedClassList + map:(nullable NSDictionary *)map; @property(nonatomic, strong, nullable) NSNumber *aNullableBool; @property(nonatomic, strong, nullable) NSNumber *aNullableInt; @property(nonatomic, strong, nullable) NSNumber *aNullableInt64; @@ -94,8 +107,6 @@ 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, copy, nullable) NSArray *aNullableList; -@property(nonatomic, copy, nullable) NSDictionary *aNullableMap; @property(nonatomic, copy, nullable) NSArray *> *nullableNestedList; @property(nonatomic, copy, nullable) NSDictionary *nullableMapWithAnnotations; @@ -104,6 +115,13 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; @property(nonatomic, strong, nullable) AllNullableTypes *allNullableTypes; +@property(nonatomic, copy, nullable) NSArray *list; +@property(nonatomic, copy, nullable) NSArray *stringList; +@property(nonatomic, copy, nullable) NSArray *intList; +@property(nonatomic, copy, nullable) NSArray *doubleList; +@property(nonatomic, copy, nullable) NSArray *boolList; +@property(nonatomic, copy, nullable) NSArray *nestedClassList; +@property(nonatomic, copy, nullable) NSDictionary *map; @end /// The primary purpose for this class is to ensure coverage of Swift structs @@ -118,15 +136,19 @@ typedef NS_ENUM(NSUInteger, AnEnum) { aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject aNullableEnum:(nullable AnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString - aNullableObject:(nullable id)aNullableObject; + aNullableObject:(nullable id)aNullableObject + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + map:(nullable NSDictionary *)map; @property(nonatomic, strong, nullable) NSNumber *aNullableBool; @property(nonatomic, strong, nullable) NSNumber *aNullableInt; @property(nonatomic, strong, nullable) NSNumber *aNullableInt64; @@ -135,8 +157,6 @@ 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, copy, nullable) NSArray *aNullableList; -@property(nonatomic, copy, nullable) NSDictionary *aNullableMap; @property(nonatomic, copy, nullable) NSArray *> *nullableNestedList; @property(nonatomic, copy, nullable) NSDictionary *nullableMapWithAnnotations; @@ -144,6 +164,12 @@ typedef NS_ENUM(NSUInteger, AnEnum) { @property(nonatomic, strong, nullable) AnEnumBox *aNullableEnum; @property(nonatomic, copy, nullable) NSString *aNullableString; @property(nonatomic, strong, nullable) id aNullableObject; +@property(nonatomic, copy, nullable) NSArray *list; +@property(nonatomic, copy, nullable) NSArray *stringList; +@property(nonatomic, copy, nullable) NSArray *intList; +@property(nonatomic, copy, nullable) NSArray *doubleList; +@property(nonatomic, copy, nullable) NSArray *boolList; +@property(nonatomic, copy, nullable) NSDictionary *map; @end /// A class for testing nested class handling. @@ -166,12 +192,12 @@ 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, copy, nullable) NSArray *testList; ++ (instancetype)makeWithTestList:(nullable NSArray *)testList; +@property(nonatomic, copy, nullable) NSArray *testList; @end -/// The codec used by HostIntegrationCoreApi. -NSObject *HostIntegrationCoreApiGetCodec(void); +/// The codec used by all APIs. +NSObject *GetCoreTestsCodec(void); /// The core interface that each host language plugin must implement in /// platform_test integration tests. @@ -219,7 +245,7 @@ NSObject *HostIntegrationCoreApiGetCodec(void); /// Returns the passed list, to test serialization and deserialization. /// /// @return `nil` only when `error != nil`. -- (nullable NSArray *)echoList:(NSArray *)aList +- (nullable NSArray *)echoList:(NSArray *)list error:(FlutterError *_Nullable *_Nonnull)error; /// Returns the passed map, to test serialization and deserialization. /// @@ -341,7 +367,7 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)echoAsyncObject:(id)anObject completion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test asynchronous serialization and deserialization. -- (void)echoAsyncList:(NSArray *)aList +- (void)echoAsyncList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test asynchronous serialization and deserialization. - (void)echoAsyncMap:(NSDictionary *)aMap @@ -391,7 +417,7 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)echoAsyncNullableObject:(nullable id)anObject completion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test asynchronous serialization and deserialization. -- (void)echoAsyncNullableList:(nullable NSArray *)aList +- (void)echoAsyncNullableList:(nullable NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test asynchronous serialization and deserialization. - (void)echoAsyncNullableMap:(nullable NSDictionary *)aMap @@ -437,10 +463,10 @@ NSObject *HostIntegrationCoreApiGetCodec(void); completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)aList +- (void)callFlutterEchoUint8List:(FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoList:(NSArray *)aList +- (void)callFlutterEchoList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoMap:(NSDictionary *)aMap completion:(void (^)(NSDictionary *_Nullable, @@ -459,10 +485,10 @@ NSObject *HostIntegrationCoreApiGetCodec(void); - (void)callFlutterEchoNullableString:(nullable NSString *)aString completion: (void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)aList +- (void)callFlutterEchoNullableUint8List:(nullable FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; -- (void)callFlutterEchoNullableList:(nullable NSArray *)aList +- (void)callFlutterEchoNullableList:(nullable NSArray *)list completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; - (void)callFlutterEchoNullableMap:(nullable NSDictionary *)aMap @@ -483,9 +509,6 @@ extern void SetUpHostIntegrationCoreApiWithSuffix(id bin NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by FlutterIntegrationCoreApi. -NSObject *FlutterIntegrationCoreApiGetCodec(void); - /// The core interface that the Dart platform_test code implements for host /// integration tests to call into. @interface FlutterIntegrationCoreApi : NSObject @@ -541,11 +564,11 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); - (void)echoString:(NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed byte list, to test serialization and deserialization. -- (void)echoUint8List:(FlutterStandardTypedData *)aList +- (void)echoUint8List:(FlutterStandardTypedData *)list completion: (void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test serialization and deserialization. -- (void)echoList:(NSArray *)aList +- (void)echoList:(NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test serialization and deserialization. - (void)echoMap:(NSDictionary *)aMap @@ -567,11 +590,11 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); - (void)echoNullableString:(nullable NSString *)aString completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed byte list, to test serialization and deserialization. -- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)aList +- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed list, to test serialization and deserialization. -- (void)echoNullableList:(nullable NSArray *)aList +- (void)echoNullableList:(nullable NSArray *)list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; /// Returns the passed map, to test serialization and deserialization. - (void)echoNullableMap:(nullable NSDictionary *)aMap @@ -588,9 +611,6 @@ NSObject *FlutterIntegrationCoreApiGetCodec(void); completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @end -/// The codec used by HostTrivialApi. -NSObject *HostTrivialApiGetCodec(void); - /// An API that can be implemented for minimal, compile-only tests. @protocol HostTrivialApi - (void)noopWithError:(FlutterError *_Nullable *_Nonnull)error; @@ -603,9 +623,6 @@ extern void SetUpHostTrivialApiWithSuffix(id binaryMesse NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by HostSmallApi. -NSObject *HostSmallApiGetCodec(void); - /// A simple API implemented in some unit tests. @protocol HostSmallApi - (void)echoString:(NSString *)aString @@ -620,9 +637,6 @@ extern void SetUpHostSmallApiWithSuffix(id binaryMesseng NSObject *_Nullable api, NSString *messageChannelSuffix); -/// The codec used by FlutterSmallApi. -NSObject *FlutterSmallApiGetCodec(void); - /// A simple API called in some unit tests. @interface FlutterSmallApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; 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 7d1fbdae824..be3810ceae8 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 @@ -17,7 +17,7 @@ #error File requires ARC to be enabled. #endif -static NSArray *wrapResult(id result, FlutterError *error) { +static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] @@ -35,7 +35,7 @@ details:@""]; } -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @@ -51,33 +51,33 @@ - (instancetype)initWithValue:(AnEnum)value { @end @interface AllTypes () -+ (AllTypes *)fromList:(NSArray *)list; -+ (nullable AllTypes *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (AllTypes *)fromList:(NSArray *)list; ++ (nullable AllTypes *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface AllNullableTypes () -+ (AllNullableTypes *)fromList:(NSArray *)list; -+ (nullable AllNullableTypes *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (AllNullableTypes *)fromList:(NSArray *)list; ++ (nullable AllNullableTypes *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface AllNullableTypesWithoutRecursion () -+ (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list; -+ (nullable AllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list; ++ (nullable AllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface AllClassesWrapper () -+ (AllClassesWrapper *)fromList:(NSArray *)list; -+ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (AllClassesWrapper *)fromList:(NSArray *)list; ++ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @interface TestMessage () -+ (TestMessage *)fromList:(NSArray *)list; -+ (nullable TestMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; ++ (TestMessage *)fromList:(NSArray *)list; ++ (nullable TestMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; @end @implementation AllTypes @@ -89,11 +89,15 @@ + (instancetype)makeWithABool:(BOOL)aBool a4ByteArray:(FlutterStandardTypedData *)a4ByteArray a8ByteArray:(FlutterStandardTypedData *)a8ByteArray aFloatArray:(FlutterStandardTypedData *)aFloatArray - aList:(NSArray *)aList - aMap:(NSDictionary *)aMap anEnum:(AnEnum)anEnum aString:(NSString *)aString - anObject:(id)anObject { + anObject:(id)anObject + list:(NSArray *)list + stringList:(NSArray *)stringList + intList:(NSArray *)intList + doubleList:(NSArray *)doubleList + boolList:(NSArray *)boolList + map:(NSDictionary *)map { AllTypes *pigeonResult = [[AllTypes alloc] init]; pigeonResult.aBool = aBool; pigeonResult.anInt = anInt; @@ -103,14 +107,18 @@ + (instancetype)makeWithABool:(BOOL)aBool pigeonResult.a4ByteArray = a4ByteArray; pigeonResult.a8ByteArray = a8ByteArray; pigeonResult.aFloatArray = aFloatArray; - pigeonResult.aList = aList; - pigeonResult.aMap = aMap; pigeonResult.anEnum = anEnum; pigeonResult.aString = aString; pigeonResult.anObject = anObject; + pigeonResult.list = list; + pigeonResult.stringList = stringList; + pigeonResult.intList = intList; + pigeonResult.doubleList = doubleList; + pigeonResult.boolList = boolList; + pigeonResult.map = map; return pigeonResult; } -+ (AllTypes *)fromList:(NSArray *)list { ++ (AllTypes *)fromList:(NSArray *)list { AllTypes *pigeonResult = [[AllTypes alloc] init]; pigeonResult.aBool = [GetNullableObjectAtIndex(list, 0) boolValue]; pigeonResult.anInt = [GetNullableObjectAtIndex(list, 1) integerValue]; @@ -120,17 +128,22 @@ + (AllTypes *)fromList:(NSArray *)list { pigeonResult.a4ByteArray = GetNullableObjectAtIndex(list, 5); pigeonResult.a8ByteArray = GetNullableObjectAtIndex(list, 6); pigeonResult.aFloatArray = GetNullableObjectAtIndex(list, 7); - pigeonResult.aList = GetNullableObjectAtIndex(list, 8); - pigeonResult.aMap = GetNullableObjectAtIndex(list, 9); - pigeonResult.anEnum = [GetNullableObjectAtIndex(list, 10) integerValue]; - pigeonResult.aString = GetNullableObjectAtIndex(list, 11); - pigeonResult.anObject = GetNullableObjectAtIndex(list, 12); + AnEnumBox *enumBox = GetNullableObjectAtIndex(list, 8); + pigeonResult.anEnum = enumBox.value; + pigeonResult.aString = GetNullableObjectAtIndex(list, 9); + pigeonResult.anObject = GetNullableObjectAtIndex(list, 10); + pigeonResult.list = GetNullableObjectAtIndex(list, 11); + pigeonResult.stringList = GetNullableObjectAtIndex(list, 12); + pigeonResult.intList = GetNullableObjectAtIndex(list, 13); + pigeonResult.doubleList = GetNullableObjectAtIndex(list, 14); + pigeonResult.boolList = GetNullableObjectAtIndex(list, 15); + pigeonResult.map = GetNullableObjectAtIndex(list, 16); return pigeonResult; } -+ (nullable AllTypes *)nullableFromList:(NSArray *)list { ++ (nullable AllTypes *)nullableFromList:(NSArray *)list { return (list) ? [AllTypes fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ @(self.aBool), @(self.anInt), @@ -140,11 +153,15 @@ - (NSArray *)toList { self.a4ByteArray ?: [NSNull null], self.a8ByteArray ?: [NSNull null], self.aFloatArray ?: [NSNull null], - self.aList ?: [NSNull null], - self.aMap ?: [NSNull null], - @(self.anEnum), + [[AnEnumBox alloc] initWithValue:self.anEnum], self.aString ?: [NSNull null], self.anObject ?: [NSNull null], + self.list ?: [NSNull null], + self.stringList ?: [NSNull null], + self.intList ?: [NSNull null], + self.doubleList ?: [NSNull null], + self.boolList ?: [NSNull null], + self.map ?: [NSNull null], ]; } @end @@ -158,8 +175,6 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations @@ -167,7 +182,14 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullableEnum:(nullable AnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString aNullableObject:(nullable id)aNullableObject - allNullableTypes:(nullable AllNullableTypes *)allNullableTypes { + allNullableTypes:(nullable AllNullableTypes *)allNullableTypes + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + nestedClassList:(nullable NSArray *)nestedClassList + map:(nullable NSDictionary *)map { AllNullableTypes *pigeonResult = [[AllNullableTypes alloc] init]; pigeonResult.aNullableBool = aNullableBool; pigeonResult.aNullableInt = aNullableInt; @@ -177,8 +199,6 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullable4ByteArray = aNullable4ByteArray; pigeonResult.aNullable8ByteArray = aNullable8ByteArray; pigeonResult.aNullableFloatArray = aNullableFloatArray; - pigeonResult.aNullableList = aNullableList; - pigeonResult.aNullableMap = aNullableMap; pigeonResult.nullableNestedList = nullableNestedList; pigeonResult.nullableMapWithAnnotations = nullableMapWithAnnotations; pigeonResult.nullableMapWithObject = nullableMapWithObject; @@ -186,9 +206,16 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullableString = aNullableString; pigeonResult.aNullableObject = aNullableObject; pigeonResult.allNullableTypes = allNullableTypes; + pigeonResult.list = list; + pigeonResult.stringList = stringList; + pigeonResult.intList = intList; + pigeonResult.doubleList = doubleList; + pigeonResult.boolList = boolList; + pigeonResult.nestedClassList = nestedClassList; + pigeonResult.map = map; return pigeonResult; } -+ (AllNullableTypes *)fromList:(NSArray *)list { ++ (AllNullableTypes *)fromList:(NSArray *)list { AllNullableTypes *pigeonResult = [[AllNullableTypes alloc] init]; pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); pigeonResult.aNullableInt = GetNullableObjectAtIndex(list, 1); @@ -198,27 +225,26 @@ + (AllNullableTypes *)fromList:(NSArray *)list { pigeonResult.aNullable4ByteArray = GetNullableObjectAtIndex(list, 5); pigeonResult.aNullable8ByteArray = GetNullableObjectAtIndex(list, 6); pigeonResult.aNullableFloatArray = GetNullableObjectAtIndex(list, 7); - pigeonResult.aNullableList = GetNullableObjectAtIndex(list, 8); - pigeonResult.aNullableMap = GetNullableObjectAtIndex(list, 9); - pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 10); - pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); - pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); - NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); - AnEnumBox *aNullableEnum = - aNullableEnumAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; - pigeonResult.aNullableEnum = aNullableEnum; - pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); - pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); - pigeonResult.allNullableTypes = - [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 16))]; + pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 8); + pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 9); + pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 10); + pigeonResult.aNullableEnum = GetNullableObjectAtIndex(list, 11); + pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 12); + pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 13); + pigeonResult.allNullableTypes = GetNullableObjectAtIndex(list, 14); + pigeonResult.list = GetNullableObjectAtIndex(list, 15); + pigeonResult.stringList = GetNullableObjectAtIndex(list, 16); + pigeonResult.intList = GetNullableObjectAtIndex(list, 17); + pigeonResult.doubleList = GetNullableObjectAtIndex(list, 18); + pigeonResult.boolList = GetNullableObjectAtIndex(list, 19); + pigeonResult.nestedClassList = GetNullableObjectAtIndex(list, 20); + pigeonResult.map = GetNullableObjectAtIndex(list, 21); return pigeonResult; } -+ (nullable AllNullableTypes *)nullableFromList:(NSArray *)list { ++ (nullable AllNullableTypes *)nullableFromList:(NSArray *)list { return (list) ? [AllNullableTypes fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.aNullableBool ?: [NSNull null], self.aNullableInt ?: [NSNull null], @@ -228,16 +254,20 @@ - (NSArray *)toList { 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.aNullableEnum ?: [NSNull null], self.aNullableString ?: [NSNull null], self.aNullableObject ?: [NSNull null], - (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), + self.allNullableTypes ?: [NSNull null], + self.list ?: [NSNull null], + self.stringList ?: [NSNull null], + self.intList ?: [NSNull null], + self.doubleList ?: [NSNull null], + self.boolList ?: [NSNull null], + self.nestedClassList ?: [NSNull null], + self.map ?: [NSNull null], ]; } @end @@ -251,15 +281,19 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool aNullable4ByteArray:(nullable FlutterStandardTypedData *)aNullable4ByteArray aNullable8ByteArray:(nullable FlutterStandardTypedData *)aNullable8ByteArray aNullableFloatArray:(nullable FlutterStandardTypedData *)aNullableFloatArray - aNullableList:(nullable NSArray *)aNullableList - aNullableMap:(nullable NSDictionary *)aNullableMap nullableNestedList:(nullable NSArray *> *)nullableNestedList nullableMapWithAnnotations: (nullable NSDictionary *)nullableMapWithAnnotations nullableMapWithObject:(nullable NSDictionary *)nullableMapWithObject aNullableEnum:(nullable AnEnumBox *)aNullableEnum aNullableString:(nullable NSString *)aNullableString - aNullableObject:(nullable id)aNullableObject { + aNullableObject:(nullable id)aNullableObject + list:(nullable NSArray *)list + stringList:(nullable NSArray *)stringList + intList:(nullable NSArray *)intList + doubleList:(nullable NSArray *)doubleList + boolList:(nullable NSArray *)boolList + map:(nullable NSDictionary *)map { AllNullableTypesWithoutRecursion *pigeonResult = [[AllNullableTypesWithoutRecursion alloc] init]; pigeonResult.aNullableBool = aNullableBool; pigeonResult.aNullableInt = aNullableInt; @@ -269,17 +303,21 @@ + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool pigeonResult.aNullable4ByteArray = aNullable4ByteArray; pigeonResult.aNullable8ByteArray = aNullable8ByteArray; pigeonResult.aNullableFloatArray = aNullableFloatArray; - pigeonResult.aNullableList = aNullableList; - pigeonResult.aNullableMap = aNullableMap; pigeonResult.nullableNestedList = nullableNestedList; pigeonResult.nullableMapWithAnnotations = nullableMapWithAnnotations; pigeonResult.nullableMapWithObject = nullableMapWithObject; pigeonResult.aNullableEnum = aNullableEnum; pigeonResult.aNullableString = aNullableString; pigeonResult.aNullableObject = aNullableObject; + pigeonResult.list = list; + pigeonResult.stringList = stringList; + pigeonResult.intList = intList; + pigeonResult.doubleList = doubleList; + pigeonResult.boolList = boolList; + pigeonResult.map = map; return pigeonResult; } -+ (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { ++ (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { AllNullableTypesWithoutRecursion *pigeonResult = [[AllNullableTypesWithoutRecursion alloc] init]; pigeonResult.aNullableBool = GetNullableObjectAtIndex(list, 0); pigeonResult.aNullableInt = GetNullableObjectAtIndex(list, 1); @@ -289,25 +327,24 @@ + (AllNullableTypesWithoutRecursion *)fromList:(NSArray *)list { pigeonResult.aNullable4ByteArray = GetNullableObjectAtIndex(list, 5); pigeonResult.aNullable8ByteArray = GetNullableObjectAtIndex(list, 6); pigeonResult.aNullableFloatArray = GetNullableObjectAtIndex(list, 7); - pigeonResult.aNullableList = GetNullableObjectAtIndex(list, 8); - pigeonResult.aNullableMap = GetNullableObjectAtIndex(list, 9); - pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 10); - pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 11); - pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 12); - NSNumber *aNullableEnumAsNumber = GetNullableObjectAtIndex(list, 13); - AnEnumBox *aNullableEnum = - aNullableEnumAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[aNullableEnumAsNumber integerValue]]; - pigeonResult.aNullableEnum = aNullableEnum; - pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 14); - pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 15); + pigeonResult.nullableNestedList = GetNullableObjectAtIndex(list, 8); + pigeonResult.nullableMapWithAnnotations = GetNullableObjectAtIndex(list, 9); + pigeonResult.nullableMapWithObject = GetNullableObjectAtIndex(list, 10); + pigeonResult.aNullableEnum = GetNullableObjectAtIndex(list, 11); + pigeonResult.aNullableString = GetNullableObjectAtIndex(list, 12); + pigeonResult.aNullableObject = GetNullableObjectAtIndex(list, 13); + pigeonResult.list = GetNullableObjectAtIndex(list, 14); + pigeonResult.stringList = GetNullableObjectAtIndex(list, 15); + pigeonResult.intList = GetNullableObjectAtIndex(list, 16); + pigeonResult.doubleList = GetNullableObjectAtIndex(list, 17); + pigeonResult.boolList = GetNullableObjectAtIndex(list, 18); + pigeonResult.map = GetNullableObjectAtIndex(list, 19); return pigeonResult; } -+ (nullable AllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list { ++ (nullable AllNullableTypesWithoutRecursion *)nullableFromList:(NSArray *)list { return (list) ? [AllNullableTypesWithoutRecursion fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.aNullableBool ?: [NSNull null], self.aNullableInt ?: [NSNull null], @@ -317,15 +354,18 @@ - (NSArray *)toList { 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.aNullableEnum ?: [NSNull null], self.aNullableString ?: [NSNull null], self.aNullableObject ?: [NSNull null], + self.list ?: [NSNull null], + self.stringList ?: [NSNull null], + self.intList ?: [NSNull null], + self.doubleList ?: [NSNull null], + self.boolList ?: [NSNull null], + self.map ?: [NSNull null], ]; } @end @@ -341,117 +381,122 @@ + (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes pigeonResult.allTypes = allTypes; return pigeonResult; } -+ (AllClassesWrapper *)fromList:(NSArray *)list { ++ (AllClassesWrapper *)fromList:(NSArray *)list { AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init]; - pigeonResult.allNullableTypes = - [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - pigeonResult.allNullableTypesWithoutRecursion = - [AllNullableTypesWithoutRecursion nullableFromList:(GetNullableObjectAtIndex(list, 1))]; - pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 2))]; + pigeonResult.allNullableTypes = GetNullableObjectAtIndex(list, 0); + pigeonResult.allNullableTypesWithoutRecursion = GetNullableObjectAtIndex(list, 1); + pigeonResult.allTypes = GetNullableObjectAtIndex(list, 2); return pigeonResult; } -+ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list { ++ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list { return (list) ? [AllClassesWrapper fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ - (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]), - (self.allNullableTypesWithoutRecursion ? [self.allNullableTypesWithoutRecursion toList] - : [NSNull null]), - (self.allTypes ? [self.allTypes toList] : [NSNull null]), + self.allNullableTypes ?: [NSNull null], + self.allNullableTypesWithoutRecursion ?: [NSNull null], + self.allTypes ?: [NSNull null], ]; } @end @implementation TestMessage -+ (instancetype)makeWithTestList:(nullable NSArray *)testList { ++ (instancetype)makeWithTestList:(nullable NSArray *)testList { TestMessage *pigeonResult = [[TestMessage alloc] init]; pigeonResult.testList = testList; return pigeonResult; } -+ (TestMessage *)fromList:(NSArray *)list { ++ (TestMessage *)fromList:(NSArray *)list { TestMessage *pigeonResult = [[TestMessage alloc] init]; pigeonResult.testList = GetNullableObjectAtIndex(list, 0); return pigeonResult; } -+ (nullable TestMessage *)nullableFromList:(NSArray *)list { ++ (nullable TestMessage *)nullableFromList:(NSArray *)list { return (list) ? [TestMessage fromList:list] : nil; } -- (NSArray *)toList { +- (NSArray *)toList { return @[ self.testList ?: [NSNull null], ]; } @end -@interface HostIntegrationCoreApiCodecReader : FlutterStandardReader +@interface CoreTestsPigeonCodecReader : FlutterStandardReader @end -@implementation HostIntegrationCoreApiCodecReader +@implementation CoreTestsPigeonCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { - case 128: - return [AllClassesWrapper fromList:[self readValue]]; case 129: - return [AllNullableTypes fromList:[self readValue]]; + return [AllTypes fromList:[self readValue]]; case 130: - return [AllNullableTypesWithoutRecursion fromList:[self readValue]]; + return [AllNullableTypes fromList:[self readValue]]; case 131: - return [AllTypes fromList:[self readValue]]; + return [AllNullableTypesWithoutRecursion fromList:[self readValue]]; case 132: + return [AllClassesWrapper fromList:[self readValue]]; + case 133: return [TestMessage fromList:[self readValue]]; + case 134: { + NSNumber *enumAsNumber = [self readValue]; + return enumAsNumber == nil ? nil + : [[AnEnumBox alloc] initWithValue:[enumAsNumber integerValue]]; + } default: return [super readValueOfType:type]; } } @end -@interface HostIntegrationCoreApiCodecWriter : FlutterStandardWriter +@interface CoreTestsPigeonCodecWriter : FlutterStandardWriter @end -@implementation HostIntegrationCoreApiCodecWriter +@implementation CoreTestsPigeonCodecWriter - (void)writeValue:(id)value { - if ([value isKindOfClass:[AllClassesWrapper class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllNullableTypes class]]) { + if ([value isKindOfClass:[AllTypes class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllNullableTypesWithoutRecursion class]]) { + } else if ([value isKindOfClass:[AllNullableTypes class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllTypes class]]) { + } else if ([value isKindOfClass:[AllNullableTypesWithoutRecursion class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[TestMessage class]]) { + } else if ([value isKindOfClass:[AllClassesWrapper class]]) { [self writeByte:132]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[TestMessage class]]) { + [self writeByte:133]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[AnEnumBox class]]) { + AnEnumBox *box = (AnEnumBox *)value; + [self writeByte:134]; + [self writeValue:(value == nil ? [NSNull null] : [NSNumber numberWithInteger:box.value])]; } else { [super writeValue:value]; } } @end -@interface HostIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter +@interface CoreTestsPigeonCodecReaderWriter : FlutterStandardReaderWriter @end -@implementation HostIntegrationCoreApiCodecReaderWriter +@implementation CoreTestsPigeonCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[HostIntegrationCoreApiCodecWriter alloc] initWithData:data]; + return [[CoreTestsPigeonCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[HostIntegrationCoreApiCodecReader alloc] initWithData:data]; + return [[CoreTestsPigeonCodecReader alloc] initWithData:data]; } @end -NSObject *HostIntegrationCoreApiGetCodec(void) { +NSObject *GetCoreTestsCodec(void) { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - HostIntegrationCoreApiCodecReaderWriter *readerWriter = - [[HostIntegrationCoreApiCodecReaderWriter alloc] init]; + CoreTestsPigeonCodecReaderWriter *readerWriter = + [[CoreTestsPigeonCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } - void SetUpHostIntegrationCoreApi(id binaryMessenger, NSObject *api) { SetUpHostIntegrationCoreApiWithSuffix(binaryMessenger, api, @""); @@ -472,7 +517,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.noop", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopWithError:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(noopWithError:)", @@ -494,14 +539,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAllTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoAllTypes:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAllTypes:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; AllTypes *output = [api echoAllTypes:arg_everything error:&error]; @@ -519,7 +564,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.throwError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(throwErrorWithError:)], @@ -542,7 +587,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.throwErrorFromVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwErrorFromVoidWithError:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -565,7 +610,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.throwFlutterError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwFlutterErrorWithError:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -588,13 +633,13 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoInt:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api echoInt:arg_anInt error:&error]; @@ -612,13 +657,13 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoDouble:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; NSNumber *output = [api echoDouble:arg_aDouble error:&error]; @@ -636,13 +681,13 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoBool:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoBool:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; NSNumber *output = [api echoBool:arg_aBool error:&error]; @@ -660,13 +705,13 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoString:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoString:arg_aString error:&error]; @@ -684,14 +729,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoUint8List:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoUint8List:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aUint8List = GetNullableObjectAtIndex(args, 0); FlutterError *error; FlutterStandardTypedData *output = [api echoUint8List:arg_aUint8List error:&error]; @@ -709,13 +754,13 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoObject:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoObject:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); FlutterError *error; id output = [api echoObject:arg_anObject error:&error]; @@ -733,16 +778,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoList:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoList:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); FlutterError *error; - NSArray *output = [api echoList:arg_aList error:&error]; + NSArray *output = [api echoList:arg_list error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -757,13 +802,13 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoMap:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoMap:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSDictionary *output = [api echoMap:arg_aMap error:&error]; @@ -781,14 +826,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoClassWrapper", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoClassWrapper:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoClassWrapper:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); FlutterError *error; AllClassesWrapper *output = [api echoClassWrapper:arg_wrapper error:&error]; @@ -806,17 +851,17 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoEnum:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoEnum:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - AnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSArray *args = message; + AnEnumBox *enumBox = GetNullableObjectAtIndex(args, 0); + AnEnum arg_anEnum = enumBox.value; FlutterError *error; - AnEnumBox *enumBox = [api echoEnum:arg_anEnum error:&error]; - NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value]; + AnEnumBox *output = [api echoEnum:arg_anEnum error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -831,14 +876,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNamedDefaultString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNamedDefaultString:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNamedDefaultString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoNamedDefaultString:arg_aString error:&error]; @@ -857,14 +902,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoOptionalDefaultDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoOptionalDefaultDouble:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoOptionalDefaultDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; NSNumber *output = [api echoOptionalDefaultDouble:arg_aDouble error:&error]; @@ -882,14 +927,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoRequiredInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoRequiredInt:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoRequiredInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; NSNumber *output = [api echoRequiredInt:arg_anInt error:&error]; @@ -907,14 +952,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAllNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAllNullableTypes:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAllNullableTypes:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; AllNullableTypes *output = [api echoAllNullableTypes:arg_everything error:&error]; @@ -934,14 +979,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAllNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAllNullableTypesWithoutRecursion:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAllNullableTypesWithoutRecursion:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); FlutterError *error; AllNullableTypesWithoutRecursion *output = @@ -962,14 +1007,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.extractNestedNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(extractNestedNullableStringFrom:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(extractNestedNullableStringFrom:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api extractNestedNullableStringFrom:arg_wrapper error:&error]; @@ -989,14 +1034,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.createNestedNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createNestedObjectWithNullableString:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(createNestedObjectWithNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_nullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; AllClassesWrapper *output = [api createNestedObjectWithNullableString:arg_nullableString @@ -1016,7 +1061,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.sendMultipleNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(sendMultipleNullableTypesABool: anInt:aString:error:)], @@ -1024,7 +1069,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"@selector(sendMultipleNullableTypesABool:anInt:aString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -1049,7 +1094,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)], @@ -1057,7 +1102,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"@selector(sendMultipleNullableTypesWithoutRecursionABool:anInt:aString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -1081,14 +1126,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoNullableInt:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoNullableInt:arg_aNullableInt error:&error]; @@ -1106,14 +1151,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableDouble:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableDouble:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableDouble = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoNullableDouble:arg_aNullableDouble error:&error]; @@ -1131,14 +1176,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoNullableBool:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableBool:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoNullableBool:arg_aNullableBool error:&error]; @@ -1156,14 +1201,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableString:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoNullableString:arg_aNullableString error:&error]; @@ -1181,14 +1226,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableUint8List:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableUint8List:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aNullableUint8List = GetNullableObjectAtIndex(args, 0); FlutterError *error; FlutterStandardTypedData *output = [api echoNullableUint8List:arg_aNullableUint8List @@ -1207,14 +1252,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNullableObject:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNullableObject:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_aNullableObject = GetNullableObjectAtIndex(args, 0); FlutterError *error; id output = [api echoNullableObject:arg_aNullableObject error:&error]; @@ -1232,14 +1277,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoNullableList:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableList:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSArray *arg_aNullableList = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSArray *output = [api echoNullableList:arg_aNullableList error:&error]; @@ -1257,14 +1302,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoNullableMap:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableMap:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aNullableMap = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSDictionary *output = [api echoNullableMap:arg_aNullableMap error:&error]; @@ -1281,22 +1326,17 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNullableEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoNullableEnum:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoNullableEnum:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - AnEnumBox *arg_anEnum = - arg_anEnumAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + NSArray *args = message; + AnEnumBox *arg_anEnum = GetNullableObjectAtIndex(args, 0); FlutterError *error; - AnEnumBox *enumBox = [api echoNullableEnum:arg_anEnum error:&error]; - NSNumber *output = enumBox == nil ? nil : [NSNumber numberWithInteger:enumBox.value]; + AnEnumBox *output = [api echoNullableEnum:arg_anEnum error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -1312,14 +1352,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoOptionalNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoOptionalNullableInt:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoOptionalNullableInt:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api echoOptionalNullableInt:arg_aNullableInt error:&error]; @@ -1338,14 +1378,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoNamedNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoNamedNullableString:error:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoNamedNullableString:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api echoNamedNullableString:arg_aNullableString error:&error]; @@ -1364,7 +1404,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.noopAsync", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(noopAsyncWithCompletion:)], @@ -1387,14 +1427,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoAsyncInt:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAsyncInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api echoAsyncInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1413,14 +1453,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncDouble:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api echoAsyncDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1439,14 +1479,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncBool:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api echoAsyncBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1465,14 +1505,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncString:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api echoAsyncString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -1491,14 +1531,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncUint8List:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aUint8List = GetNullableObjectAtIndex(args, 0); [api echoAsyncUint8List:arg_aUint8List completion:^(FlutterStandardTypedData *_Nullable output, @@ -1518,14 +1558,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncObject:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncObject:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); [api echoAsyncObject:arg_anObject completion:^(id _Nullable output, FlutterError *_Nullable error) { @@ -1544,16 +1584,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncList:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api echoAsyncList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api echoAsyncList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1570,14 +1610,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(echoAsyncMap:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoAsyncMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api echoAsyncMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -1597,19 +1637,18 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncEnum:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - AnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSArray *args = message; + AnEnumBox *enumBox = GetNullableObjectAtIndex(args, 0); + AnEnum arg_anEnum = enumBox.value; [api echoAsyncEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; + completion:^(AnEnumBox *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; @@ -1625,7 +1664,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.throwAsyncError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncErrorWithCompletion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -1649,7 +1688,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.throwAsyncErrorFromVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncErrorFromVoidWithCompletion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -1672,7 +1711,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.throwAsyncFlutterError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(throwAsyncFlutterErrorWithCompletion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -1696,14 +1735,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncAllTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncAllTypes:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncAllTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncAllTypes:arg_everything completion:^(AllTypes *_Nullable output, FlutterError *_Nullable error) { @@ -1723,14 +1762,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableAllNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableAllNullableTypes:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableAllNullableTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableAllNullableTypes:arg_everything completion:^(AllNullableTypes *_Nullable output, @@ -1752,7 +1791,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"echoAsyncNullableAllNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)], @@ -1760,7 +1799,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"@selector(echoAsyncNullableAllNullableTypesWithoutRecursion:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableAllNullableTypesWithoutRecursion:arg_everything completion:^(AllNullableTypesWithoutRecursion @@ -1781,14 +1820,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableInt:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1808,14 +1847,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableDouble:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1834,14 +1873,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableBool:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -1861,14 +1900,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableString:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -1888,14 +1927,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableUint8List:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; FlutterStandardTypedData *arg_aUint8List = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableUint8List:arg_aUint8List completion:^(FlutterStandardTypedData *_Nullable output, @@ -1916,14 +1955,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableObject", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableObject:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableObject:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; id arg_anObject = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableObject:arg_anObject completion:^(id _Nullable output, FlutterError *_Nullable error) { @@ -1942,16 +1981,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableList:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api echoAsyncNullableList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api echoAsyncNullableList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -1968,14 +2007,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableMap:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api echoAsyncNullableMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -1995,26 +2034,19 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.echoAsyncNullableEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoAsyncNullableEnum:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(echoAsyncNullableEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - AnEnumBox *arg_anEnum = - arg_anEnumAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; - [api - echoAsyncNullableEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; - callback(wrapResult(output, error)); - }]; + NSArray *args = message; + AnEnumBox *arg_anEnum = GetNullableObjectAtIndex(args, 0); + [api echoAsyncNullableEnum:arg_anEnum + completion:^(AnEnumBox *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; }]; } else { [channel setMessageHandler:nil]; @@ -2027,7 +2059,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterNoop", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterNoopWithCompletion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -2049,7 +2081,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterThrowError", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterThrowErrorWithCompletion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -2073,7 +2105,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterThrowErrorFromVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterThrowErrorFromVoidWithCompletion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @@ -2096,14 +2128,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoAllTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoAllTypes:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoAllTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllTypes:arg_everything completion:^(AllTypes *_Nullable output, FlutterError *_Nullable error) { @@ -2122,14 +2154,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoAllNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoAllNullableTypes:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoAllNullableTypes:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllNullableTypes *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllNullableTypes:arg_everything completion:^(AllNullableTypes *_Nullable output, @@ -2150,7 +2182,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterSendMultipleNullableTypes", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (callFlutterSendMultipleNullableTypesABool:anInt:aString:completion:)], @@ -2158,7 +2190,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"@selector(callFlutterSendMultipleNullableTypesABool:anInt:aString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -2183,7 +2215,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"callFlutterEchoAllNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (callFlutterEchoAllNullableTypesWithoutRecursion:completion:)], @@ -2191,7 +2223,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"@selector(callFlutterEchoAllNullableTypesWithoutRecursion:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; AllNullableTypesWithoutRecursion *arg_everything = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoAllNullableTypesWithoutRecursion:arg_everything completion:^(AllNullableTypesWithoutRecursion @@ -2213,7 +2245,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"callFlutterSendMultipleNullableTypesWithoutRecursion", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector @@ -2224,7 +2256,7 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aNullableBool = GetNullableObjectAtIndex(args, 0); NSNumber *arg_aNullableInt = GetNullableObjectAtIndex(args, 1); NSString *arg_aNullableString = GetNullableObjectAtIndex(args, 2); @@ -2249,14 +2281,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoBool:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; BOOL arg_aBool = [GetNullableObjectAtIndex(args, 0) boolValue]; [api callFlutterEchoBool:arg_aBool completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -2274,14 +2306,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoInt:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSInteger arg_anInt = [GetNullableObjectAtIndex(args, 0) integerValue]; [api callFlutterEchoInt:arg_anInt completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -2299,14 +2331,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoDouble:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; double arg_aDouble = [GetNullableObjectAtIndex(args, 0) doubleValue]; [api callFlutterEchoDouble:arg_aDouble completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { @@ -2324,14 +2356,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoString:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -2350,16 +2382,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoUint8List:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FlutterStandardTypedData *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoUint8List:arg_aList + NSArray *args = message; + FlutterStandardTypedData *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoUint8List:arg_list completion:^(FlutterStandardTypedData *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2376,16 +2408,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoList:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; @@ -2401,14 +2433,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoMap:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -2427,19 +2459,18 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoEnum:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - AnEnum arg_anEnum = [GetNullableObjectAtIndex(args, 0) integerValue]; + NSArray *args = message; + AnEnumBox *enumBox = GetNullableObjectAtIndex(args, 0); + AnEnum arg_anEnum = enumBox.value; [api callFlutterEchoEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil : [NSNumber numberWithInteger:enumValue.value]; + completion:^(AnEnumBox *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; @@ -2455,14 +2486,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableBool", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableBool:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableBool:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aBool = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableBool:arg_aBool completion:^(NSNumber *_Nullable output, @@ -2482,14 +2513,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableInt", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableInt:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableInt:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_anInt = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableInt:arg_anInt completion:^(NSNumber *_Nullable output, @@ -2509,14 +2540,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableDouble", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableDouble:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableDouble:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSNumber *arg_aDouble = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableDouble:arg_aDouble completion:^(NSNumber *_Nullable output, @@ -2536,14 +2567,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableString:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableString:arg_aString completion:^(NSString *_Nullable output, @@ -2563,16 +2594,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableUint8List", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableUint8List:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableUint8List:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - FlutterStandardTypedData *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoNullableUint8List:arg_aList + NSArray *args = message; + FlutterStandardTypedData *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoNullableUint8List:arg_list completion:^(FlutterStandardTypedData *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2590,16 +2621,16 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableList", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableList:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableList:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_aList = GetNullableObjectAtIndex(args, 0); - [api callFlutterEchoNullableList:arg_aList + NSArray *args = message; + NSArray *arg_list = GetNullableObjectAtIndex(args, 0); + [api callFlutterEchoNullableList:arg_list completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); @@ -2617,14 +2648,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableMap", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableMap:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableMap:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSDictionary *arg_aMap = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableMap:arg_aMap completion:^(NSDictionary *_Nullable output, @@ -2644,25 +2675,18 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterEchoNullableEnum", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterEchoNullableEnum:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterEchoNullableEnum:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSNumber *arg_anEnumAsNumber = GetNullableObjectAtIndex(args, 0); - AnEnumBox *arg_anEnum = - arg_anEnumAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[arg_anEnumAsNumber integerValue]]; + NSArray *args = message; + AnEnumBox *arg_anEnum = GetNullableObjectAtIndex(args, 0); [api callFlutterEchoNullableEnum:arg_anEnum - completion:^(AnEnumBox *_Nullable enumValue, + completion:^(AnEnumBox *_Nullable output, FlutterError *_Nullable error) { - NSNumber *output = - enumValue == nil ? nil - : [NSNumber numberWithInteger:enumValue.value]; callback(wrapResult(output, error)); }]; }]; @@ -2678,14 +2702,14 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess @"HostIntegrationCoreApi.callFlutterSmallApiEchoString", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(callFlutterSmallApiEchoString:completion:)], @"HostIntegrationCoreApi api (%@) doesn't respond to " @"@selector(callFlutterSmallApiEchoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api callFlutterSmallApiEchoString:arg_aString completion:^(NSString *_Nullable output, @@ -2698,74 +2722,6 @@ void SetUpHostIntegrationCoreApiWithSuffix(id binaryMess } } } -@interface FlutterIntegrationCoreApiCodecReader : FlutterStandardReader -@end -@implementation FlutterIntegrationCoreApiCodecReader -- (nullable id)readValueOfType:(UInt8)type { - switch (type) { - case 128: - return [AllClassesWrapper fromList:[self readValue]]; - case 129: - return [AllNullableTypes fromList:[self readValue]]; - case 130: - return [AllNullableTypesWithoutRecursion fromList:[self readValue]]; - case 131: - return [AllTypes fromList:[self readValue]]; - case 132: - return [TestMessage fromList:[self readValue]]; - default: - return [super readValueOfType:type]; - } -} -@end - -@interface FlutterIntegrationCoreApiCodecWriter : FlutterStandardWriter -@end -@implementation FlutterIntegrationCoreApiCodecWriter -- (void)writeValue:(id)value { - if ([value isKindOfClass:[AllClassesWrapper class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllNullableTypes class]]) { - [self writeByte:129]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllNullableTypesWithoutRecursion class]]) { - [self writeByte:130]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[AllTypes class]]) { - [self writeByte:131]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[TestMessage class]]) { - [self writeByte:132]; - [self writeValue:[value toList]]; - } else { - [super writeValue:value]; - } -} -@end - -@interface FlutterIntegrationCoreApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation FlutterIntegrationCoreApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FlutterIntegrationCoreApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FlutterIntegrationCoreApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *FlutterIntegrationCoreApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - static dispatch_once_t sPred = 0; - dispatch_once(&sPred, ^{ - FlutterIntegrationCoreApiCodecReaderWriter *readerWriter = - [[FlutterIntegrationCoreApiCodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - @interface FlutterIntegrationCoreApi () @property(nonatomic, strong) NSObject *binaryMessenger; @property(nonatomic, strong) NSString *messageChannelSuffix; @@ -2795,7 +2751,7 @@ - (void)noopWithCompletion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2820,7 +2776,7 @@ - (void)throwErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2846,7 +2802,7 @@ - (void)throwErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))comp FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -2872,7 +2828,7 @@ - (void)echoAllTypes:(AllTypes *)arg_everything FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2900,7 +2856,7 @@ - (void)echoAllNullableTypes:(nullable AllNullableTypes *)arg_everything FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2930,7 +2886,7 @@ - (void)sendMultipleNullableTypesABool:(nullable NSNumber *)arg_aNullableBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], arg_aNullableString ?: [NSNull null] @@ -2962,7 +2918,7 @@ - (void)echoAllNullableTypesWithoutRecursion: FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_everything ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -2995,7 +2951,7 @@ - (void)echoAllNullableTypesWithoutRecursion: FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aNullableBool ?: [NSNull null], arg_aNullableInt ?: [NSNull null], arg_aNullableString ?: [NSNull null] @@ -3026,7 +2982,7 @@ - (void)echoBool:(BOOL)arg_aBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ @(arg_aBool) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3052,7 +3008,7 @@ - (void)echoInt:(NSInteger)arg_anInt FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ @(arg_anInt) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3079,7 +3035,7 @@ - (void)echoDouble:(double)arg_aDouble FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ @(arg_aDouble) ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3106,7 +3062,7 @@ - (void)echoString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3123,7 +3079,7 @@ - (void)echoString:(NSString *)arg_aString } }]; } -- (void)echoUint8List:(FlutterStandardTypedData *)arg_aList +- (void)echoUint8List:(FlutterStandardTypedData *)arg_list completion: (void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = [NSString @@ -3134,8 +3090,8 @@ - (void)echoUint8List:(FlutterStandardTypedData *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:GetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3152,7 +3108,7 @@ - (void)echoUint8List:(FlutterStandardTypedData *)arg_aList } }]; } -- (void)echoList:(NSArray *)arg_aList +- (void)echoList:(NSArray *)arg_list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = [NSString stringWithFormat: @@ -3162,8 +3118,8 @@ - (void)echoList:(NSArray *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:GetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3189,7 +3145,7 @@ - (void)echoMap:(NSDictionary *)arg_aMap FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aMap ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3217,8 +3173,8 @@ - (void)echoEnum:(AnEnum)arg_anEnum FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ [NSNumber numberWithInteger:arg_anEnum] ] + codec:GetCoreTestsCodec()]; + [channel sendMessage:@[ [[AnEnumBox alloc] initWithValue:arg_anEnum] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3226,11 +3182,7 @@ - (void)echoEnum:(AnEnum)arg_anEnum message:reply[1] details:reply[2]]); } else { - NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; - AnEnumBox *output = - outputAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; + AnEnumBox *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -3248,7 +3200,7 @@ - (void)echoNullableBool:(nullable NSNumber *)arg_aBool FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aBool ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3275,7 +3227,7 @@ - (void)echoNullableInt:(nullable NSNumber *)arg_anInt FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_anInt ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3302,7 +3254,7 @@ - (void)echoNullableDouble:(nullable NSNumber *)arg_aDouble FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aDouble ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3329,7 +3281,7 @@ - (void)echoNullableString:(nullable NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3346,7 +3298,7 @@ - (void)echoNullableString:(nullable NSString *)arg_aString } }]; } -- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList +- (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_list completion:(void (^)(FlutterStandardTypedData *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = @@ -3357,8 +3309,8 @@ - (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:GetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3375,7 +3327,7 @@ - (void)echoNullableUint8List:(nullable FlutterStandardTypedData *)arg_aList } }]; } -- (void)echoNullableList:(nullable NSArray *)arg_aList +- (void)echoNullableList:(nullable NSArray *)arg_list completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { NSString *channelName = [NSString stringWithFormat: @@ -3385,8 +3337,8 @@ - (void)echoNullableList:(nullable NSArray *)arg_aList FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_aList ?: [NSNull null] ] + codec:GetCoreTestsCodec()]; + [channel sendMessage:@[ arg_list ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3413,7 +3365,7 @@ - (void)echoNullableMap:(nullable NSDictionary *)arg_aMap FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aMap ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3441,9 +3393,8 @@ - (void)echoNullableEnum:(nullable AnEnumBox *)arg_anEnum FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; - [channel sendMessage:@[ arg_anEnum == nil ? [NSNull null] - : [NSNumber numberWithInteger:arg_anEnum.value] ] + codec:GetCoreTestsCodec()]; + [channel sendMessage:@[ arg_anEnum == nil ? [NSNull null] : arg_anEnum ] reply:^(NSArray *reply) { if (reply != nil) { if (reply.count > 1) { @@ -3451,11 +3402,7 @@ - (void)echoNullableEnum:(nullable AnEnumBox *)arg_anEnum message:reply[1] details:reply[2]]); } else { - NSNumber *outputAsNumber = reply[0] == [NSNull null] ? nil : reply[0]; - AnEnumBox *output = - outputAsNumber == nil - ? nil - : [[AnEnumBox alloc] initWithValue:[outputAsNumber integerValue]]; + AnEnumBox *output = reply[0] == [NSNull null] ? nil : reply[0]; completion(output, nil); } } else { @@ -3472,7 +3419,7 @@ - (void)noopAsyncWithCompletion:(void (^)(FlutterError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:nil reply:^(NSArray *reply) { if (reply != nil) { @@ -3498,7 +3445,7 @@ - (void)echoAsyncString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterIntegrationCoreApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3517,12 +3464,6 @@ - (void)echoAsyncString:(NSString *)arg_aString } @end -NSObject *HostTrivialApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - sSharedObject = [FlutterStandardMessageCodec sharedInstance]; - return sSharedObject; -} - void SetUpHostTrivialApi(id binaryMessenger, NSObject *api) { SetUpHostTrivialApiWithSuffix(binaryMessenger, api, @""); @@ -3541,7 +3482,7 @@ void SetUpHostTrivialApiWithSuffix(id binaryMessenger, @"dev.flutter.pigeon.pigeon_integration_tests.HostTrivialApi.noop", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostTrivialApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(noopWithError:)], @"HostTrivialApi api (%@) doesn't respond to @selector(noopWithError:)", api); @@ -3555,12 +3496,6 @@ void SetUpHostTrivialApiWithSuffix(id binaryMessenger, } } } -NSObject *HostSmallApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - sSharedObject = [FlutterStandardMessageCodec sharedInstance]; - return sSharedObject; -} - void SetUpHostSmallApi(id binaryMessenger, NSObject *api) { SetUpHostSmallApiWithSuffix(binaryMessenger, api, @""); } @@ -3578,12 +3513,12 @@ void SetUpHostSmallApiWithSuffix(id binaryMessenger, @"dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostSmallApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(echoString:completion:)], @"HostSmallApi api (%@) doesn't respond to @selector(echoString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; + NSArray *args = message; NSString *arg_aString = GetNullableObjectAtIndex(args, 0); [api echoString:arg_aString completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { @@ -3601,7 +3536,7 @@ void SetUpHostSmallApiWithSuffix(id binaryMessenger, @"HostSmallApi.voidVoid", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:HostSmallApiGetCodec()]; + codec:GetCoreTestsCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(voidVoidWithCompletion:)], @"HostSmallApi api (%@) doesn't respond to @selector(voidVoidWithCompletion:)", @@ -3616,54 +3551,6 @@ void SetUpHostSmallApiWithSuffix(id binaryMessenger, } } } -@interface FlutterSmallApiCodecReader : FlutterStandardReader -@end -@implementation FlutterSmallApiCodecReader -- (nullable id)readValueOfType:(UInt8)type { - switch (type) { - case 128: - return [TestMessage fromList:[self readValue]]; - default: - return [super readValueOfType:type]; - } -} -@end - -@interface FlutterSmallApiCodecWriter : FlutterStandardWriter -@end -@implementation FlutterSmallApiCodecWriter -- (void)writeValue:(id)value { - if ([value isKindOfClass:[TestMessage class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else { - [super writeValue:value]; - } -} -@end - -@interface FlutterSmallApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation FlutterSmallApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FlutterSmallApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FlutterSmallApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *FlutterSmallApiGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - static dispatch_once_t sPred = 0; - dispatch_once(&sPred, ^{ - FlutterSmallApiCodecReaderWriter *readerWriter = - [[FlutterSmallApiCodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - @interface FlutterSmallApi () @property(nonatomic, strong) NSObject *binaryMessenger; @property(nonatomic, strong) NSString *messageChannelSuffix; @@ -3694,7 +3581,7 @@ - (void)echoWrappedList:(TestMessage *)arg_msg FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterSmallApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_msg ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { @@ -3720,7 +3607,7 @@ - (void)echoString:(NSString *)arg_aString FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:channelName binaryMessenger:self.binaryMessenger - codec:FlutterSmallApiGetCodec()]; + codec:GetCoreTestsCodec()]; [channel sendMessage:@[ arg_aString ?: [NSNull null] ] reply:^(NSArray *reply) { if (reply != nil) { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/pubspec.yaml b/packages/pigeon/platform_tests/alternate_language_test_plugin/pubspec.yaml index b9bca67acb8..eb8c109ccfc 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/pubspec.yaml +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/pubspec.yaml @@ -4,8 +4,8 @@ version: 0.0.1 publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart index caa448cac36..ba3ac04c1c9 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart @@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'generated.dart'; +import 'src/generated/core_tests.gen.dart'; const int _biggerThanBigInt = 3000000000; const int _regularInt = 42; @@ -49,10 +50,14 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { expect(allTypesOne.a4ByteArray, allTypesTwo.a4ByteArray); expect(allTypesOne.a8ByteArray, allTypesTwo.a8ByteArray); expect(allTypesOne.aFloatArray, allTypesTwo.aFloatArray); - expect(listEquals(allTypesOne.aList, allTypesTwo.aList), true); - expect(mapEquals(allTypesOne.aMap, allTypesTwo.aMap), true); expect(allTypesOne.anEnum, allTypesTwo.anEnum); expect(allTypesOne.anObject, allTypesTwo.anObject); + expect(listEquals(allTypesOne.list, allTypesTwo.list), true); + expect(listEquals(allTypesOne.stringList, allTypesTwo.stringList), true); + expect(listEquals(allTypesOne.boolList, allTypesTwo.boolList), true); + expect(listEquals(allTypesOne.doubleList, allTypesTwo.doubleList), true); + expect(listEquals(allTypesOne.intList, allTypesTwo.intList), true); + expect(mapEquals(allTypesOne.map, allTypesTwo.map), true); } void compareAllNullableTypes(AllNullableTypes? allNullableTypesOne, @@ -78,14 +83,6 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { allNullableTypesTwo.aNullable8ByteArray); expect(allNullableTypesOne.aNullableFloatArray, allNullableTypesTwo.aNullableFloatArray); - expect( - listEquals(allNullableTypesOne.aNullableList, - allNullableTypesTwo.aNullableList), - true); - expect( - mapEquals( - allNullableTypesOne.aNullableMap, allNullableTypesTwo.aNullableMap), - true); expect(allNullableTypesOne.nullableNestedList?.length, allNullableTypesTwo.nullableNestedList?.length); // TODO(stuartmorgan): Enable this once the Dart types are fixed; see @@ -108,6 +105,30 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { allNullableTypesOne.aNullableEnum, allNullableTypesTwo.aNullableEnum); compareAllNullableTypes(allNullableTypesOne.allNullableTypes, allNullableTypesTwo.allNullableTypes); + expect( + listEquals(allNullableTypesOne.list, allNullableTypesTwo.list), true); + expect( + listEquals( + allNullableTypesOne.stringList, allNullableTypesTwo.stringList), + true); + expect( + listEquals(allNullableTypesOne.boolList, allNullableTypesTwo.boolList), + true); + expect( + listEquals( + allNullableTypesOne.doubleList, allNullableTypesTwo.doubleList), + true); + expect(listEquals(allNullableTypesOne.intList, allNullableTypesTwo.intList), + true); + expect(allNullableTypesOne.nestedClassList?.length, + allNullableTypesTwo.nestedClassList?.length); + for (int i = 0; + i < (allNullableTypesOne.nestedClassList?.length ?? 0); + i++) { + compareAllNullableTypes(allNullableTypesOne.nestedClassList?[i], + allNullableTypesTwo.nestedClassList?[i]); + } + expect(mapEquals(allNullableTypesOne.map, allNullableTypesTwo.map), true); } void compareAllNullableTypesWithoutRecursion( @@ -134,14 +155,6 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { allNullableTypesTwo.aNullable8ByteArray); expect(allNullableTypesOne.aNullableFloatArray, allNullableTypesTwo.aNullableFloatArray); - expect( - listEquals(allNullableTypesOne.aNullableList, - allNullableTypesTwo.aNullableList), - true); - expect( - mapEquals( - allNullableTypesOne.aNullableMap, allNullableTypesTwo.aNullableMap), - true); expect(allNullableTypesOne.nullableNestedList?.length, allNullableTypesTwo.nullableNestedList?.length); // TODO(stuartmorgan): Enable this once the Dart types are fixed; see @@ -162,6 +175,22 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { allNullableTypesTwo.aNullableObject); expect( allNullableTypesOne.aNullableEnum, allNullableTypesTwo.aNullableEnum); + expect( + listEquals(allNullableTypesOne.list, allNullableTypesTwo.list), true); + expect( + listEquals( + allNullableTypesOne.stringList, allNullableTypesTwo.stringList), + true); + expect( + listEquals(allNullableTypesOne.boolList, allNullableTypesTwo.boolList), + true); + expect( + listEquals( + allNullableTypesOne.doubleList, allNullableTypesTwo.doubleList), + true); + expect(listEquals(allNullableTypesOne.intList, allNullableTypesTwo.intList), + true); + expect(mapEquals(allNullableTypesOne.map, allNullableTypesTwo.map), true); } void compareAllClassesWrapper( @@ -180,6 +209,54 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { compareAllTypes(wrapperOne.allTypes, wrapperTwo.allTypes); } + final Map map = { + 'a': 1, + 'b': 2.0, + 'c': 'three', + 'd': false, + 'e': null + }; + + final List list = [ + 'Thing 1', + 2, + true, + 3.14, + null, + ]; + + final List stringList = [ + 'Thing 1', + '2', + 'true', + '3.14', + null, + ]; + + final List intList = [ + 1, + 2, + 3, + 4, + null, + ]; + + final List doubleList = [ + 1, + 2.99999, + 3, + 3.14, + null, + ]; + + final List boolList = [ + true, + false, + true, + false, + null, + ]; + final AllTypes genericAllTypes = AllTypes( aBool: true, anInt: _regularInt, @@ -190,16 +267,14 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { a4ByteArray: Int32List.fromList([4, 5, 6]), a8ByteArray: Int64List.fromList([7, 8, 9]), aFloatArray: Float64List.fromList([2.71828, _doublePi]), - aList: ['Thing 1', 2, true, 3.14, null], - aMap: { - 'a': 1, - 'b': 2.0, - 'c': 'three', - 'd': false, - 'e': null - }, anEnum: AnEnum.fortyTwo, anObject: 1, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map, ); final AllNullableTypes genericAllNullableTypes = AllNullableTypes( @@ -212,14 +287,6 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { aNullable4ByteArray: Int32List.fromList([4, 5, 6]), aNullable8ByteArray: Int64List.fromList([7, 8, 9]), aNullableFloatArray: Float64List.fromList([2.71828, _doublePi]), - aNullableList: ['Thing 1', 2, true, 3.14, null], - aNullableMap: { - 'a': 1, - 'b': 2.0, - 'c': 'three', - 'd': false, - 'e': null - }, nullableNestedList: >[ [true, false], [false, true] @@ -228,8 +295,20 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { nullableMapWithObject: {}, aNullableEnum: AnEnum.fourHundredTwentyTwo, aNullableObject: 0, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map, ); + final List allNullableTypesList = [ + genericAllNullableTypes, + AllNullableTypes(), + null, + ]; + final AllNullableTypes recursiveAllNullableTypes = AllNullableTypes( aNullableBool: true, aNullableInt: _regularInt, @@ -240,14 +319,6 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { aNullable4ByteArray: Int32List.fromList([4, 5, 6]), aNullable8ByteArray: Int64List.fromList([7, 8, 9]), aNullableFloatArray: Float64List.fromList([2.71828, _doublePi]), - aNullableList: ['Thing 1', 2, true, 3.14, null], - aNullableMap: { - 'a': 1, - 'b': 2.0, - 'c': 'three', - 'd': false, - 'e': null - }, nullableNestedList: >[ [true, false], [false, true] @@ -257,6 +328,13 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { aNullableEnum: AnEnum.fourHundredTwentyTwo, aNullableObject: 0, allNullableTypes: genericAllNullableTypes, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + nestedClassList: allNullableTypesList, + map: map, ); final AllNullableTypesWithoutRecursion @@ -271,14 +349,6 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { aNullable4ByteArray: Int32List.fromList([4, 5, 6]), aNullable8ByteArray: Int64List.fromList([7, 8, 9]), aNullableFloatArray: Float64List.fromList([2.71828, _doublePi]), - aNullableList: ['Thing 1', 2, true, 3.14, null], - aNullableMap: { - 'a': 1, - 'b': 2.0, - 'c': 'three', - 'd': false, - 'e': null - }, nullableNestedList: >[ [true, false], [false, true] @@ -287,6 +357,12 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { nullableMapWithObject: {}, aNullableEnum: AnEnum.fourHundredTwentyTwo, aNullableObject: 0, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map, ); group('Host sync API tests', () { @@ -330,7 +406,7 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); final AllNullableTypes nullableListTypes = - AllNullableTypes(aNullableList: ['String', null]); + AllNullableTypes(list: ['String', null]); final AllNullableTypes? echoNullFilledClass = await api.echoAllNullableTypes(nullableListTypes); @@ -343,7 +419,7 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); final AllNullableTypes nullableListTypes = AllNullableTypes( - aNullableMap: {'String': 'string', 'null': null}); + map: {'String': 'string', 'null': null}); final AllNullableTypes? echoNullFilledClass = await api.echoAllNullableTypes(nullableListTypes); @@ -385,7 +461,8 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final AllNullableTypesWithoutRecursion nullableListTypes = AllNullableTypesWithoutRecursion( - aNullableList: ['String', null]); + list: ['String', null], + ); final AllNullableTypesWithoutRecursion? echoNullFilledClass = await api.echoAllNullableTypesWithoutRecursion(nullableListTypes); @@ -400,10 +477,9 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); final AllNullableTypesWithoutRecursion nullableListTypes = - AllNullableTypesWithoutRecursion(aNullableMap: { - 'String': 'string', - 'null': null - }); + AllNullableTypesWithoutRecursion( + map: {'String': 'string', 'null': null}, + ); final AllNullableTypesWithoutRecursion? echoNullFilledClass = await api.echoAllNullableTypesWithoutRecursion(nullableListTypes); @@ -907,6 +983,15 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { expect(echoEnum, sentEnum); }); + testWidgets('null classes serialize and deserialize correctly', + (WidgetTester _) async { + final HostIntegrationCoreApi api = HostIntegrationCoreApi(); + + final AllNullableTypes? echoObject = await api.echoAllNullableTypes(null); + + expect(echoObject, isNull); + }); + testWidgets('optional nullable parameter', (WidgetTester _) async { final HostIntegrationCoreApi api = HostIntegrationCoreApi(); @@ -1863,10 +1948,10 @@ class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { String echoString(String aString) => aString; @override - Uint8List echoUint8List(Uint8List aList) => aList; + Uint8List echoUint8List(Uint8List list) => list; @override - List echoList(List aList) => aList; + List echoList(List list) => list; @override Map echoMap(Map aMap) => aMap; @@ -1884,7 +1969,7 @@ class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { int? echoNullableInt(int? anInt) => anInt; @override - List? echoNullableList(List? aList) => aList; + List? echoNullableList(List? list) => list; @override Map? echoNullableMap(Map? aMap) => aMap; @@ -1893,7 +1978,7 @@ class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { String? echoNullableString(String? aString) => aString; @override - Uint8List? echoNullableUint8List(Uint8List? aList) => aList; + Uint8List? echoNullableUint8List(Uint8List? list) => list; @override AnEnum? echoNullableEnum(AnEnum? anEnum) => anEnum; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/background_platform_channels.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/background_platform_channels.gen.dart index 461683120ce..e9e063126c1 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/background_platform_channels.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/background_platform_channels.gen.dart @@ -19,6 +19,10 @@ PlatformException _createConnectionError(String channelName) { ); } +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + class BackgroundApi2Host { /// Constructor for [BackgroundApi2Host]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -30,8 +34,7 @@ class BackgroundApi2Host { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart index 9c164946626..e192a84ceea 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart @@ -49,11 +49,15 @@ class AllTypes { required this.a4ByteArray, required this.a8ByteArray, required this.aFloatArray, - this.aList = const [], - this.aMap = const {}, this.anEnum = AnEnum.one, this.aString = '', this.anObject = 0, + required this.list, + required this.stringList, + required this.intList, + required this.doubleList, + required this.boolList, + required this.map, }); bool aBool; @@ -72,16 +76,24 @@ class AllTypes { Float64List aFloatArray; - List aList; - - Map aMap; - AnEnum anEnum; String aString; Object anObject; + List list; + + List stringList; + + List intList; + + List doubleList; + + List boolList; + + Map map; + Object encode() { return [ aBool, @@ -92,11 +104,15 @@ class AllTypes { a4ByteArray, a8ByteArray, aFloatArray, - aList, - aMap, - anEnum.index, + anEnum, aString, anObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ]; } @@ -111,11 +127,15 @@ class AllTypes { a4ByteArray: result[5]! as Int32List, a8ByteArray: result[6]! as Int64List, aFloatArray: result[7]! as Float64List, - aList: result[8]! as List, - aMap: result[9]! as Map, - anEnum: AnEnum.values[result[10]! as int], - aString: result[11]! as String, - anObject: result[12]!, + anEnum: result[8]! as AnEnum, + aString: result[9]! as String, + anObject: result[10]!, + list: result[11]! as List, + stringList: (result[12] as List?)!.cast(), + intList: (result[13] as List?)!.cast(), + doubleList: (result[14] as List?)!.cast(), + boolList: (result[15] as List?)!.cast(), + map: result[16]! as Map, ); } } @@ -131,8 +151,6 @@ class AllNullableTypes { this.aNullable4ByteArray, this.aNullable8ByteArray, this.aNullableFloatArray, - this.aNullableList, - this.aNullableMap, this.nullableNestedList, this.nullableMapWithAnnotations, this.nullableMapWithObject, @@ -140,6 +158,13 @@ class AllNullableTypes { this.aNullableString, this.aNullableObject, this.allNullableTypes, + this.list, + this.stringList, + this.intList, + this.doubleList, + this.boolList, + this.nestedClassList, + this.map, }); bool? aNullableBool; @@ -158,10 +183,6 @@ class AllNullableTypes { Float64List? aNullableFloatArray; - List? aNullableList; - - Map? aNullableMap; - List?>? nullableNestedList; Map? nullableMapWithAnnotations; @@ -176,6 +197,20 @@ class AllNullableTypes { AllNullableTypes? allNullableTypes; + List? list; + + List? stringList; + + List? intList; + + List? doubleList; + + List? boolList; + + List? nestedClassList; + + Map? map; + Object encode() { return [ aNullableBool, @@ -186,15 +221,20 @@ class AllNullableTypes { aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.index, + aNullableEnum, aNullableString, aNullableObject, - allNullableTypes?.encode(), + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + nestedClassList, + map, ]; } @@ -209,20 +249,23 @@ class AllNullableTypes { aNullable4ByteArray: result[5] as Int32List?, aNullable8ByteArray: result[6] as Int64List?, aNullableFloatArray: result[7] as Float64List?, - aNullableList: result[8] as List?, - aNullableMap: result[9] as Map?, - nullableNestedList: (result[10] as List?)?.cast?>(), + nullableNestedList: (result[8] as List?)?.cast?>(), nullableMapWithAnnotations: - (result[11] as Map?)?.cast(), + (result[9] as Map?)?.cast(), nullableMapWithObject: - (result[12] as Map?)?.cast(), - aNullableEnum: - result[13] != null ? AnEnum.values[result[13]! as int] : null, - aNullableString: result[14] as String?, - aNullableObject: result[15], - allNullableTypes: result[16] != null - ? AllNullableTypes.decode(result[16]! as List) - : null, + (result[10] as Map?)?.cast(), + aNullableEnum: result[11] as AnEnum?, + aNullableString: result[12] as String?, + aNullableObject: result[13], + allNullableTypes: result[14] as AllNullableTypes?, + list: result[15] as List?, + stringList: (result[16] as List?)?.cast(), + intList: (result[17] as List?)?.cast(), + doubleList: (result[18] as List?)?.cast(), + boolList: (result[19] as List?)?.cast(), + nestedClassList: + (result[20] as List?)?.cast(), + map: result[21] as Map?, ); } } @@ -240,14 +283,18 @@ class AllNullableTypesWithoutRecursion { this.aNullable4ByteArray, this.aNullable8ByteArray, this.aNullableFloatArray, - this.aNullableList, - this.aNullableMap, this.nullableNestedList, this.nullableMapWithAnnotations, this.nullableMapWithObject, this.aNullableEnum, this.aNullableString, this.aNullableObject, + this.list, + this.stringList, + this.intList, + this.doubleList, + this.boolList, + this.map, }); bool? aNullableBool; @@ -266,10 +313,6 @@ class AllNullableTypesWithoutRecursion { Float64List? aNullableFloatArray; - List? aNullableList; - - Map? aNullableMap; - List?>? nullableNestedList; Map? nullableMapWithAnnotations; @@ -282,6 +325,18 @@ class AllNullableTypesWithoutRecursion { Object? aNullableObject; + List? list; + + List? stringList; + + List? intList; + + List? doubleList; + + List? boolList; + + Map? map; + Object encode() { return [ aNullableBool, @@ -292,14 +347,18 @@ class AllNullableTypesWithoutRecursion { aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.index, + aNullableEnum, aNullableString, aNullableObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ]; } @@ -314,17 +373,20 @@ class AllNullableTypesWithoutRecursion { aNullable4ByteArray: result[5] as Int32List?, aNullable8ByteArray: result[6] as Int64List?, aNullableFloatArray: result[7] as Float64List?, - aNullableList: result[8] as List?, - aNullableMap: result[9] as Map?, - nullableNestedList: (result[10] as List?)?.cast?>(), + nullableNestedList: (result[8] as List?)?.cast?>(), nullableMapWithAnnotations: - (result[11] as Map?)?.cast(), + (result[9] as Map?)?.cast(), nullableMapWithObject: - (result[12] as Map?)?.cast(), - aNullableEnum: - result[13] != null ? AnEnum.values[result[13]! as int] : null, - aNullableString: result[14] as String?, - aNullableObject: result[15], + (result[10] as Map?)?.cast(), + aNullableEnum: result[11] as AnEnum?, + aNullableString: result[12] as String?, + aNullableObject: result[13], + list: result[14] as List?, + stringList: (result[15] as List?)?.cast(), + intList: (result[16] as List?)?.cast(), + doubleList: (result[17] as List?)?.cast(), + boolList: (result[18] as List?)?.cast(), + map: result[19] as Map?, ); } } @@ -349,22 +411,19 @@ class AllClassesWrapper { Object encode() { return [ - allNullableTypes.encode(), - allNullableTypesWithoutRecursion?.encode(), - allTypes?.encode(), + allNullableTypes, + allNullableTypesWithoutRecursion, + allTypes, ]; } static AllClassesWrapper decode(Object result) { result as List; return AllClassesWrapper( - allNullableTypes: AllNullableTypes.decode(result[0]! as List), - allNullableTypesWithoutRecursion: result[1] != null - ? AllNullableTypesWithoutRecursion.decode(result[1]! as List) - : null, - allTypes: result[2] != null - ? AllTypes.decode(result[2]! as List) - : null, + allNullableTypes: result[0]! as AllNullableTypes, + allNullableTypesWithoutRecursion: + result[1] as AllNullableTypesWithoutRecursion?, + allTypes: result[2] as AllTypes?, ); } } @@ -391,25 +450,28 @@ class TestMessage { } } -class _HostIntegrationCoreApiCodec extends StandardMessageCodec { - const _HostIntegrationCoreApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is AllClassesWrapper) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is AllNullableTypes) { + if (value is AllTypes) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is AllNullableTypesWithoutRecursion) { + } else if (value is AllNullableTypes) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is AllTypes) { + } else if (value is AllNullableTypesWithoutRecursion) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is TestMessage) { + } else if (value is AllClassesWrapper) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is TestMessage) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is AnEnum) { + buffer.putUint8(134); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -418,16 +480,19 @@ class _HostIntegrationCoreApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return AllClassesWrapper.decode(readValue(buffer)!); case 129: - return AllNullableTypes.decode(readValue(buffer)!); + return AllTypes.decode(readValue(buffer)!); case 130: - return AllNullableTypesWithoutRecursion.decode(readValue(buffer)!); + return AllNullableTypes.decode(readValue(buffer)!); case 131: - return AllTypes.decode(readValue(buffer)!); + return AllNullableTypesWithoutRecursion.decode(readValue(buffer)!); case 132: + return AllClassesWrapper.decode(readValue(buffer)!); + case 133: return TestMessage.decode(readValue(buffer)!); + case 134: + final int? value = readValue(buffer) as int?; + return value == null ? null : AnEnum.values[value]; default: return super.readValueOfType(type, buffer); } @@ -447,8 +512,7 @@ class HostIntegrationCoreApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - _HostIntegrationCoreApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -764,7 +828,7 @@ class HostIntegrationCoreApi { } /// Returns the passed list, to test serialization and deserialization. - Future> echoList(List aList) async { + Future> echoList(List list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoList$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -774,7 +838,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -865,7 +929,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([anEnum.index]) as List?; + await __pigeon_channel.send([anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -880,7 +944,7 @@ class HostIntegrationCoreApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return AnEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as AnEnum?)!; } } @@ -1363,7 +1427,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([anEnum?.index]) as List?; + await __pigeon_channel.send([anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -1373,9 +1437,7 @@ class HostIntegrationCoreApi { details: __pigeon_replyList[2], ); } else { - return (__pigeon_replyList[0] as int?) == null - ? null - : AnEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as AnEnum?); } } @@ -1636,7 +1698,7 @@ class HostIntegrationCoreApi { } /// Returns the passed list, to test asynchronous serialization and deserialization. - Future> echoAsyncList(List aList) async { + Future> echoAsyncList(List list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncList$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -1646,7 +1708,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -1707,7 +1769,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([anEnum.index]) as List?; + await __pigeon_channel.send([anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -1722,7 +1784,7 @@ class HostIntegrationCoreApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return AnEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as AnEnum?)!; } } @@ -2035,7 +2097,7 @@ class HostIntegrationCoreApi { } /// Returns the passed list, to test asynchronous serialization and deserialization. - Future?> echoAsyncNullableList(List? aList) async { + Future?> echoAsyncNullableList(List? list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.echoAsyncNullableList$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -2045,7 +2107,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2097,7 +2159,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([anEnum?.index]) as List?; + await __pigeon_channel.send([anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2107,9 +2169,7 @@ class HostIntegrationCoreApi { details: __pigeon_replyList[2], ); } else { - return (__pigeon_replyList[0] as int?) == null - ? null - : AnEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as AnEnum?); } } @@ -2444,7 +2504,7 @@ class HostIntegrationCoreApi { } } - Future callFlutterEchoUint8List(Uint8List aList) async { + Future callFlutterEchoUint8List(Uint8List list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoUint8List$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -2454,7 +2514,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2473,7 +2533,7 @@ class HostIntegrationCoreApi { } } - Future> callFlutterEchoList(List aList) async { + Future> callFlutterEchoList(List list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoList$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -2483,7 +2543,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2543,7 +2603,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([anEnum.index]) as List?; + await __pigeon_channel.send([anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2558,7 +2618,7 @@ class HostIntegrationCoreApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return AnEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as AnEnum?)!; } } @@ -2658,7 +2718,7 @@ class HostIntegrationCoreApi { } } - Future callFlutterEchoNullableUint8List(Uint8List? aList) async { + Future callFlutterEchoNullableUint8List(Uint8List? list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoNullableUint8List$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -2668,7 +2728,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2683,7 +2743,7 @@ class HostIntegrationCoreApi { } Future?> callFlutterEchoNullableList( - List? aList) async { + List? list) async { final String __pigeon_channelName = 'dev.flutter.pigeon.pigeon_integration_tests.HostIntegrationCoreApi.callFlutterEchoNullableList$__pigeon_messageChannelSuffix'; final BasicMessageChannel __pigeon_channel = @@ -2693,7 +2753,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([aList]) as List?; + await __pigeon_channel.send([list]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2743,7 +2803,7 @@ class HostIntegrationCoreApi { binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = - await __pigeon_channel.send([anEnum?.index]) as List?; + await __pigeon_channel.send([anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2753,9 +2813,7 @@ class HostIntegrationCoreApi { details: __pigeon_replyList[2], ); } else { - return (__pigeon_replyList[0] as int?) == null - ? null - : AnEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as AnEnum?); } } @@ -2789,54 +2847,10 @@ class HostIntegrationCoreApi { } } -class _FlutterIntegrationCoreApiCodec extends StandardMessageCodec { - const _FlutterIntegrationCoreApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is AllClassesWrapper) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is AllNullableTypes) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is AllNullableTypesWithoutRecursion) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is AllTypes) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else if (value is TestMessage) { - buffer.putUint8(132); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return AllClassesWrapper.decode(readValue(buffer)!); - case 129: - return AllNullableTypes.decode(readValue(buffer)!); - case 130: - return AllNullableTypesWithoutRecursion.decode(readValue(buffer)!); - case 131: - return AllTypes.decode(readValue(buffer)!); - case 132: - return TestMessage.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - /// The core interface that the Dart platform_test code implements for host /// integration tests to call into. abstract class FlutterIntegrationCoreApi { - static const MessageCodec pigeonChannelCodec = - _FlutterIntegrationCoreApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. @@ -2883,10 +2897,10 @@ abstract class FlutterIntegrationCoreApi { String echoString(String aString); /// Returns the passed byte list, to test serialization and deserialization. - Uint8List echoUint8List(Uint8List aList); + Uint8List echoUint8List(Uint8List list); /// Returns the passed list, to test serialization and deserialization. - List echoList(List aList); + List echoList(List list); /// Returns the passed map, to test serialization and deserialization. Map echoMap(Map aMap); @@ -2907,10 +2921,10 @@ abstract class FlutterIntegrationCoreApi { String? echoNullableString(String? aString); /// Returns the passed byte list, to test serialization and deserialization. - Uint8List? echoNullableUint8List(Uint8List? aList); + Uint8List? echoNullableUint8List(Uint8List? list); /// Returns the passed list, to test serialization and deserialization. - List? echoNullableList(List? aList); + List? echoNullableList(List? list); /// Returns the passed map, to test serialization and deserialization. Map? echoNullableMap(Map? aMap); @@ -3266,11 +3280,11 @@ abstract class FlutterIntegrationCoreApi { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoUint8List was null.'); final List args = (message as List?)!; - final Uint8List? arg_aList = (args[0] as Uint8List?); - assert(arg_aList != null, + final Uint8List? arg_list = (args[0] as Uint8List?); + assert(arg_list != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoUint8List was null, expected non-null Uint8List.'); try { - final Uint8List output = api.echoUint8List(arg_aList!); + final Uint8List output = api.echoUint8List(arg_list!); return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -3294,12 +3308,12 @@ abstract class FlutterIntegrationCoreApi { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoList was null.'); final List args = (message as List?)!; - final List? arg_aList = + final List? arg_list = (args[0] as List?)?.cast(); - assert(arg_aList != null, + assert(arg_list != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoList was null, expected non-null List.'); try { - final List output = api.echoList(arg_aList!); + final List output = api.echoList(arg_list!); return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -3352,13 +3366,12 @@ abstract class FlutterIntegrationCoreApi { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoEnum was null.'); final List args = (message as List?)!; - final AnEnum? arg_anEnum = - args[0] == null ? null : AnEnum.values[args[0]! as int]; + final AnEnum? arg_anEnum = (args[0] as AnEnum?); assert(arg_anEnum != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoEnum was null, expected non-null AnEnum.'); try { final AnEnum output = api.echoEnum(arg_anEnum!); - return wrapResponse(result: output.index); + return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { @@ -3485,9 +3498,9 @@ abstract class FlutterIntegrationCoreApi { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableUint8List was null.'); final List args = (message as List?)!; - final Uint8List? arg_aList = (args[0] as Uint8List?); + final Uint8List? arg_list = (args[0] as Uint8List?); try { - final Uint8List? output = api.echoNullableUint8List(arg_aList); + final Uint8List? output = api.echoNullableUint8List(arg_list); return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -3511,10 +3524,10 @@ abstract class FlutterIntegrationCoreApi { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableList was null.'); final List args = (message as List?)!; - final List? arg_aList = + final List? arg_list = (args[0] as List?)?.cast(); try { - final List? output = api.echoNullableList(arg_aList); + final List? output = api.echoNullableList(arg_list); return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -3565,11 +3578,10 @@ abstract class FlutterIntegrationCoreApi { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableEnum was null.'); final List args = (message as List?)!; - final AnEnum? arg_anEnum = - args[0] == null ? null : AnEnum.values[args[0]! as int]; + final AnEnum? arg_anEnum = (args[0] as AnEnum?); try { final AnEnum? output = api.echoNullableEnum(arg_anEnum); - return wrapResponse(result: output?.index); + return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { @@ -3644,8 +3656,7 @@ class HostTrivialApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -3686,8 +3697,7 @@ class HostSmallApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -3745,33 +3755,9 @@ class HostSmallApi { } } -class _FlutterSmallApiCodec extends StandardMessageCodec { - const _FlutterSmallApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is TestMessage) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return TestMessage.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - /// A simple API called in some unit tests. abstract class FlutterSmallApi { - static const MessageCodec pigeonChannelCodec = - _FlutterSmallApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); TestMessage echoWrappedList(TestMessage msg); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/enum.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/enum.gen.dart index 374f272c358..e2ca92672ed 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/enum.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/enum.gen.dart @@ -56,25 +56,28 @@ class DataWithEnum { Object encode() { return [ - state?.index, + state, ]; } static DataWithEnum decode(Object result) { result as List; return DataWithEnum( - state: result[0] != null ? EnumState.values[result[0]! as int] : null, + state: result[0] as EnumState?, ); } } -class _EnumApi2HostCodec extends StandardMessageCodec { - const _EnumApi2HostCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is DataWithEnum) { - buffer.putUint8(128); + buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is EnumState) { + buffer.putUint8(130); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -83,8 +86,11 @@ class _EnumApi2HostCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 129: return DataWithEnum.decode(readValue(buffer)!); + case 130: + final int? value = readValue(buffer) as int?; + return value == null ? null : EnumState.values[value]; default: return super.readValueOfType(type, buffer); } @@ -103,7 +109,7 @@ class EnumApi2Host { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = _EnumApi2HostCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -138,33 +144,9 @@ class EnumApi2Host { } } -class _EnumApi2FlutterCodec extends StandardMessageCodec { - const _EnumApi2FlutterCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is DataWithEnum) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return DataWithEnum.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - /// This comment is to test api documentation comments. abstract class EnumApi2Flutter { - static const MessageCodec pigeonChannelCodec = - _EnumApi2FlutterCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); /// This comment is to test method documentation comments. DataWithEnum echo(DataWithEnum data); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/flutter_unittests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/flutter_unittests.gen.dart index b37dffb2557..61e4b97a5fe 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/flutter_unittests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/flutter_unittests.gen.dart @@ -108,22 +108,22 @@ class FlutterSearchReplies { } } -class _ApiCodec extends StandardMessageCodec { - const _ApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is FlutterSearchReplies) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is FlutterSearchReply) { + if (value is FlutterSearchRequest) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is FlutterSearchRequest) { + } else if (value is FlutterSearchReply) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is FlutterSearchRequests) { buffer.putUint8(131); writeValue(buffer, value.encode()); + } else if (value is FlutterSearchReplies) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -132,14 +132,14 @@ class _ApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return FlutterSearchReplies.decode(readValue(buffer)!); case 129: - return FlutterSearchReply.decode(readValue(buffer)!); - case 130: return FlutterSearchRequest.decode(readValue(buffer)!); + case 130: + return FlutterSearchReply.decode(readValue(buffer)!); case 131: return FlutterSearchRequests.decode(readValue(buffer)!); + case 132: + return FlutterSearchReplies.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -156,7 +156,7 @@ class Api { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = _ApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/message.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/message.gen.dart index 044b792995e..fb7e65c9483 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/message.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/message.gen.dart @@ -103,7 +103,7 @@ class MessageSearchReply { return [ result, error, - state?.index, + state, ]; } @@ -112,9 +112,7 @@ class MessageSearchReply { return MessageSearchReply( result: result[0] as String?, error: result[1] as String?, - state: result[2] != null - ? MessageRequestState.values[result[2]! as int] - : null, + state: result[2] as MessageRequestState?, ); } } @@ -130,30 +128,34 @@ class MessageNested { Object encode() { return [ - request?.encode(), + request, ]; } static MessageNested decode(Object result) { result as List; return MessageNested( - request: result[0] != null - ? MessageSearchRequest.decode(result[0]! as List) - : null, + request: result[0] as MessageSearchRequest?, ); } } -class _MessageApiCodec extends StandardMessageCodec { - const _MessageApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is MessageSearchReply) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchRequest) { + if (value is MessageSearchRequest) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is MessageSearchReply) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is MessageNested) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is MessageRequestState) { + buffer.putUint8(132); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -162,10 +164,15 @@ class _MessageApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return MessageSearchReply.decode(readValue(buffer)!); case 129: return MessageSearchRequest.decode(readValue(buffer)!); + case 130: + return MessageSearchReply.decode(readValue(buffer)!); + case 131: + return MessageNested.decode(readValue(buffer)!); + case 132: + final int? value = readValue(buffer) as int?; + return value == null ? null : MessageRequestState.values[value]; default: return super.readValueOfType(type, buffer); } @@ -186,7 +193,7 @@ class MessageApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = _MessageApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -248,39 +255,6 @@ class MessageApi { } } -class _MessageNestedApiCodec extends StandardMessageCodec { - const _MessageNestedApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is MessageNested) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchReply) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchRequest) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return MessageNested.decode(readValue(buffer)!); - case 129: - return MessageSearchReply.decode(readValue(buffer)!); - case 130: - return MessageSearchRequest.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - /// This comment is to test api documentation comments. class MessageNestedApi { /// Constructor for [MessageNestedApi]. The [binaryMessenger] named argument is @@ -293,8 +267,7 @@ class MessageNestedApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - _MessageNestedApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -331,38 +304,9 @@ class MessageNestedApi { } } -class _MessageFlutterSearchApiCodec extends StandardMessageCodec { - const _MessageFlutterSearchApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is MessageSearchReply) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchRequest) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return MessageSearchReply.decode(readValue(buffer)!); - case 129: - return MessageSearchRequest.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - /// This comment is to test api documentation comments. abstract class MessageFlutterSearchApi { - static const MessageCodec pigeonChannelCodec = - _MessageFlutterSearchApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); /// This comment is to test method documentation comments. MessageSearchReply search(MessageSearchRequest request); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/multiple_arity.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/multiple_arity.gen.dart index 61bd5d14094..33b984077af 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/multiple_arity.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/multiple_arity.gen.dart @@ -30,6 +30,10 @@ List wrapResponse( return [error.code, error.message, error.details]; } +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + class MultipleArityHostApi { /// Constructor for [MultipleArityHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -41,8 +45,7 @@ class MultipleArityHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -77,8 +80,7 @@ class MultipleArityHostApi { } abstract class MultipleArityFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); int subtract(int x, int y); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/non_null_fields.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/non_null_fields.gen.dart index dcfe61bb0ad..c9c7fe5a638 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/non_null_fields.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/non_null_fields.gen.dart @@ -106,8 +106,8 @@ class NonNullFieldSearchReply { result, error, indices, - extraData.encode(), - type.index, + extraData, + type, ]; } @@ -117,25 +117,28 @@ class NonNullFieldSearchReply { result: result[0]! as String, error: result[1]! as String, indices: (result[2] as List?)!.cast(), - extraData: ExtraData.decode(result[3]! as List), - type: ReplyType.values[result[4]! as int], + extraData: result[3]! as ExtraData, + type: result[4]! as ReplyType, ); } } -class _NonNullFieldHostApiCodec extends StandardMessageCodec { - const _NonNullFieldHostApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is ExtraData) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is NonNullFieldSearchReply) { + if (value is NonNullFieldSearchRequest) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is NonNullFieldSearchRequest) { + } else if (value is ExtraData) { buffer.putUint8(130); writeValue(buffer, value.encode()); + } else if (value is NonNullFieldSearchReply) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is ReplyType) { + buffer.putUint8(132); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -144,12 +147,15 @@ class _NonNullFieldHostApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return ExtraData.decode(readValue(buffer)!); case 129: - return NonNullFieldSearchReply.decode(readValue(buffer)!); - case 130: return NonNullFieldSearchRequest.decode(readValue(buffer)!); + case 130: + return ExtraData.decode(readValue(buffer)!); + case 131: + return NonNullFieldSearchReply.decode(readValue(buffer)!); + case 132: + final int? value = readValue(buffer) as int?; + return value == null ? null : ReplyType.values[value]; default: return super.readValueOfType(type, buffer); } @@ -167,8 +173,7 @@ class NonNullFieldHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - _NonNullFieldHostApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -203,42 +208,8 @@ class NonNullFieldHostApi { } } -class _NonNullFieldFlutterApiCodec extends StandardMessageCodec { - const _NonNullFieldFlutterApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is ExtraData) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is NonNullFieldSearchReply) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is NonNullFieldSearchRequest) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return ExtraData.decode(readValue(buffer)!); - case 129: - return NonNullFieldSearchReply.decode(readValue(buffer)!); - case 130: - return NonNullFieldSearchRequest.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - abstract class NonNullFieldFlutterApi { - static const MessageCodec pigeonChannelCodec = - _NonNullFieldFlutterApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); NonNullFieldSearchReply search(NonNullFieldSearchRequest request); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/null_fields.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/null_fields.gen.dart index 940dd315b48..578a7c28c31 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/null_fields.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/null_fields.gen.dart @@ -85,8 +85,8 @@ class NullFieldsSearchReply { result, error, indices, - request?.encode(), - type?.index, + request, + type, ]; } @@ -96,26 +96,25 @@ class NullFieldsSearchReply { result: result[0] as String?, error: result[1] as String?, indices: (result[2] as List?)?.cast(), - request: result[3] != null - ? NullFieldsSearchRequest.decode(result[3]! as List) - : null, - type: result[4] != null - ? NullFieldsSearchReplyType.values[result[4]! as int] - : null, + request: result[3] as NullFieldsSearchRequest?, + type: result[4] as NullFieldsSearchReplyType?, ); } } -class _NullFieldsHostApiCodec extends StandardMessageCodec { - const _NullFieldsHostApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is NullFieldsSearchReply) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is NullFieldsSearchRequest) { + if (value is NullFieldsSearchRequest) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is NullFieldsSearchReply) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is NullFieldsSearchReplyType) { + buffer.putUint8(131); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -124,10 +123,13 @@ class _NullFieldsHostApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return NullFieldsSearchReply.decode(readValue(buffer)!); case 129: return NullFieldsSearchRequest.decode(readValue(buffer)!); + case 130: + return NullFieldsSearchReply.decode(readValue(buffer)!); + case 131: + final int? value = readValue(buffer) as int?; + return value == null ? null : NullFieldsSearchReplyType.values[value]; default: return super.readValueOfType(type, buffer); } @@ -145,8 +147,7 @@ class NullFieldsHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - _NullFieldsHostApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -180,37 +181,8 @@ class NullFieldsHostApi { } } -class _NullFieldsFlutterApiCodec extends StandardMessageCodec { - const _NullFieldsFlutterApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is NullFieldsSearchReply) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is NullFieldsSearchRequest) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return NullFieldsSearchReply.decode(readValue(buffer)!); - case 129: - return NullFieldsSearchRequest.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - abstract class NullFieldsFlutterApi { - static const MessageCodec pigeonChannelCodec = - _NullFieldsFlutterApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); NullFieldsSearchReply search(NullFieldsSearchRequest request); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/nullable_returns.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/nullable_returns.gen.dart index 39348701f18..72ea0ae7363 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/nullable_returns.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/nullable_returns.gen.dart @@ -30,6 +30,10 @@ List wrapResponse( return [error.code, error.message, error.details]; } +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + class NullableReturnHostApi { /// Constructor for [NullableReturnHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -41,8 +45,7 @@ class NullableReturnHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -72,8 +75,7 @@ class NullableReturnHostApi { } abstract class NullableReturnFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); int? doit(); @@ -120,8 +122,7 @@ class NullableArgHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -156,8 +157,7 @@ class NullableArgHostApi { } abstract class NullableArgFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); int? doit(int? x); @@ -208,8 +208,7 @@ class NullableCollectionReturnHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -239,8 +238,7 @@ class NullableCollectionReturnHostApi { } abstract class NullableCollectionReturnFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); List? doit(); @@ -287,8 +285,7 @@ class NullableCollectionArgHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -323,8 +320,7 @@ class NullableCollectionArgHostApi { } abstract class NullableCollectionArgFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); List doit(List? x); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/primitive.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/primitive.gen.dart index 846d67e6099..8ac914eb6fa 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/primitive.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/primitive.gen.dart @@ -30,6 +30,10 @@ List wrapResponse( return [error.code, error.message, error.details]; } +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); +} + class PrimitiveHostApi { /// Constructor for [PrimitiveHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -41,8 +45,7 @@ class PrimitiveHostApi { messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); final String __pigeon_messageChannelSuffix; @@ -310,8 +313,7 @@ class PrimitiveHostApi { } abstract class PrimitiveFlutterApi { - static const MessageCodec pigeonChannelCodec = - StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); int anInt(int value); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart index 762f65d5aad..97dbbc67559 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart @@ -284,56 +284,67 @@ class PigeonInstanceManager { class _PigeonInstanceManagerApi { /// Constructor for [_PigeonInstanceManagerApi]. _PigeonInstanceManagerApi({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; + : __pigeon_binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; + final BinaryMessenger? __pigeon_binaryMessenger; static const MessageCodec pigeonChannelCodec = StandardMessageCodec(); static void setUpMessageHandlers({ + bool pigeon_clearHandlers = false, BinaryMessenger? binaryMessenger, PigeonInstanceManager? instanceManager, }) { - const String channelName = - r'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference'; - final BasicMessageChannel channel = BasicMessageChannel( - channelName, - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for $channelName was null.', - ); - final int? identifier = message as int?; - assert( - identifier != null, - r'Argument for $channelName, expected non-null int.', - ); - (instanceManager ?? PigeonInstanceManager.instance).remove(identifier!); - return; - }); + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + __pigeon_channel.setMessageHandler(null); + } else { + __pigeon_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference was null, expected non-null int.'); + try { + (instanceManager ?? PigeonInstanceManager.instance) + .remove(arg_identifier!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } Future removeStrongReference(int identifier) async { - const String channelName = - r'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference'; - final BasicMessageChannel channel = BasicMessageChannel( - channelName, + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.removeStrongReference'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, pigeonChannelCodec, - binaryMessenger: _binaryMessenger, + binaryMessenger: __pigeon_binaryMessenger, ); - final List? replyList = - await channel.send(identifier) as List?; - if (replyList == null) { - throw _createConnectionError(channelName); - } else if (replyList.length > 1) { + final List? __pigeon_replyList = + await __pigeon_channel.send([identifier]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); } else { return; @@ -344,21 +355,23 @@ class _PigeonInstanceManagerApi { /// /// This is typically called after a hot restart. Future clear() async { - const String channelName = - r'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.clear'; - final BasicMessageChannel channel = BasicMessageChannel( - channelName, + const String __pigeon_channelName = + 'dev.flutter.pigeon.pigeon_integration_tests.PigeonInstanceManagerApi.clear'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, pigeonChannelCodec, - binaryMessenger: _binaryMessenger, + binaryMessenger: __pigeon_binaryMessenger, ); - final List? replyList = await channel.send(null) as List?; - if (replyList == null) { - throw _createConnectionError(channelName); - } else if (replyList.length > 1) { + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); } else { return; @@ -366,7 +379,7 @@ class _PigeonInstanceManagerApi { } } -class _PigeonProxyApiBaseCodec extends StandardMessageCodec { +class _PigeonProxyApiBaseCodec extends _PigeonCodec { const _PigeonProxyApiBaseCodec(this.instanceManager); final PigeonInstanceManager instanceManager; @override @@ -397,6 +410,30 @@ enum ProxyApiTestEnum { three, } +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ProxyApiTestEnum) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : ProxyApiTestEnum.values[value]; + default: + return super.readValueOfType(type, buffer); + } + } +} + /// The core ProxyApi test class that each supported host language must /// implement in platform_tests integration tests. class ProxyApiTestClass extends ProxyApiSuperClass @@ -491,7 +528,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass aUint8List, aList, aMap, - anEnum.index, + anEnum, aProxyApi, aNullableBool, aNullableInt, @@ -500,7 +537,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass aNullableUint8List, aNullableList, aNullableMap, - aNullableEnum?.index, + aNullableEnum, aNullableProxyApi, boolParam, intParam, @@ -509,7 +546,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass aUint8ListParam, listParam, mapParam, - enumParam.index, + enumParam, proxyApiParam, nullableBoolParam, nullableIntParam, @@ -518,7 +555,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass nullableUint8ListParam, nullableListParam, nullableMapParam, - nullableEnumParam?.index, + nullableEnumParam, nullableProxyApiParam ]) as List?; if (__pigeon_replyList == null) { @@ -1386,8 +1423,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass (args[7] as Map?)?.cast(); assert(arg_aMap != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null Map.'); - final ProxyApiTestEnum? arg_anEnum = - args[8] == null ? null : ProxyApiTestEnum.values[args[8]! as int]; + final ProxyApiTestEnum? arg_anEnum = (args[8] as ProxyApiTestEnum?); assert(arg_anEnum != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.pigeon_newInstance was null, expected non-null ProxyApiTestEnum.'); final ProxyApiSuperClass? arg_aProxyApi = @@ -1403,9 +1439,8 @@ class ProxyApiTestClass extends ProxyApiSuperClass (args[15] as List?)?.cast(); final Map? arg_aNullableMap = (args[16] as Map?)?.cast(); - final ProxyApiTestEnum? arg_aNullableEnum = args[17] == null - ? null - : ProxyApiTestEnum.values[args[17]! as int]; + final ProxyApiTestEnum? arg_aNullableEnum = + (args[17] as ProxyApiTestEnum?); final ProxyApiSuperClass? arg_aNullableProxyApi = (args[18] as ProxyApiSuperClass?); try { @@ -1898,15 +1933,14 @@ class ProxyApiTestClass extends ProxyApiSuperClass (args[0] as ProxyApiTestClass?); assert(arg_pigeon_instance != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoEnum was null, expected non-null ProxyApiTestClass.'); - final ProxyApiTestEnum? arg_anEnum = - args[1] == null ? null : ProxyApiTestEnum.values[args[1]! as int]; + final ProxyApiTestEnum? arg_anEnum = (args[1] as ProxyApiTestEnum?); assert(arg_anEnum != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoEnum was null, expected non-null ProxyApiTestEnum.'); try { final ProxyApiTestEnum? output = (flutterEchoEnum ?? arg_pigeon_instance!.flutterEchoEnum) ?.call(arg_pigeon_instance!, arg_anEnum!); - return wrapResponse(result: output?.index); + return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { @@ -2203,13 +2237,12 @@ class ProxyApiTestClass extends ProxyApiSuperClass (args[0] as ProxyApiTestClass?); assert(arg_pigeon_instance != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.ProxyApiTestClass.flutterEchoNullableEnum was null, expected non-null ProxyApiTestClass.'); - final ProxyApiTestEnum? arg_anEnum = - args[1] == null ? null : ProxyApiTestEnum.values[args[1]! as int]; + final ProxyApiTestEnum? arg_anEnum = (args[1] as ProxyApiTestEnum?); try { final ProxyApiTestEnum? output = (flutterEchoNullableEnum ?? arg_pigeon_instance!.flutterEchoNullableEnum) ?.call(arg_pigeon_instance!, arg_anEnum); - return wrapResponse(result: output?.index); + return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { @@ -2856,8 +2889,8 @@ class ProxyApiTestClass extends ProxyApiSuperClass pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, ); - final List? __pigeon_replyList = await __pigeon_channel - .send([this, anEnum.index]) as List?; + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -2872,7 +2905,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass message: 'Host platform returned null value for non-null return value.', ); } else { - return ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as ProxyApiTestEnum?)!; } } @@ -3150,7 +3183,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass binaryMessenger: __pigeon_binaryMessenger, ); final List? __pigeon_replyList = await __pigeon_channel - .send([this, aNullableEnum?.index]) as List?; + .send([this, aNullableEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -3160,9 +3193,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass details: __pigeon_replyList[2], ); } else { - return (__pigeon_replyList[0] as int?) == null - ? null - : ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as ProxyApiTestEnum?); } } @@ -3502,8 +3533,8 @@ class ProxyApiTestClass extends ProxyApiSuperClass pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, ); - final List? __pigeon_replyList = await __pigeon_channel - .send([this, anEnum.index]) as List?; + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -3518,7 +3549,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass message: 'Host platform returned null value for non-null return value.', ); } else { - return ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as ProxyApiTestEnum?)!; } } @@ -3846,8 +3877,8 @@ class ProxyApiTestClass extends ProxyApiSuperClass pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, ); - final List? __pigeon_replyList = await __pigeon_channel - .send([this, anEnum?.index]) as List?; + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -3857,9 +3888,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass details: __pigeon_replyList[2], ); } else { - return (__pigeon_replyList[0] as int?) == null - ? null - : ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as ProxyApiTestEnum?); } } @@ -4349,8 +4378,8 @@ class ProxyApiTestClass extends ProxyApiSuperClass pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, ); - final List? __pigeon_replyList = await __pigeon_channel - .send([this, anEnum.index]) as List?; + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -4365,7 +4394,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass message: 'Host platform returned null value for non-null return value.', ); } else { - return ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as ProxyApiTestEnum?)!; } } @@ -4608,8 +4637,8 @@ class ProxyApiTestClass extends ProxyApiSuperClass pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, ); - final List? __pigeon_replyList = await __pigeon_channel - .send([this, anEnum?.index]) as List?; + final List? __pigeon_replyList = + await __pigeon_channel.send([this, anEnum]) as List?; if (__pigeon_replyList == null) { throw _createConnectionError(__pigeon_channelName); } else if (__pigeon_replyList.length > 1) { @@ -4619,9 +4648,7 @@ class ProxyApiTestClass extends ProxyApiSuperClass details: __pigeon_replyList[2], ); } else { - return (__pigeon_replyList[0] as int?) == null - ? null - : ProxyApiTestEnum.values[__pigeon_replyList[0]! as int]; + return (__pigeon_replyList[0] as ProxyApiTestEnum?); } } diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/pubspec.yaml b/packages/pigeon/platform_tests/shared_test_plugin_code/pubspec.yaml index 52144d23ea3..9392c3a13cd 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/pubspec.yaml +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/pubspec.yaml @@ -4,8 +4,8 @@ version: 0.0.1 publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: build_runner: ^2.1.10 diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/genered_dart_test_code_test.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/generated_dart_test_code_test.dart similarity index 95% rename from packages/pigeon/platform_tests/shared_test_plugin_code/test/genered_dart_test_code_test.dart rename to packages/pigeon/platform_tests/shared_test_plugin_code/test/generated_dart_test_code_test.dart index a0632cf3486..27e622c597e 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/test/genered_dart_test_code_test.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/generated_dart_test_code_test.dart @@ -44,7 +44,7 @@ void main() { test('simple', () async { final MessageNestedApi api = MessageNestedApi(); final MockNested mock = MockNested(); - TestNestedApi.setup(mock); + TestNestedApi.setUp(mock); final MessageSearchReply reply = await api.search(MessageNested()..request = null); expect(mock.didCall, true); @@ -54,7 +54,7 @@ void main() { test('nested', () async { final MessageApi api = MessageApi(); final Mock mock = Mock(); - TestHostApi.setup(mock); + TestHostApi.setUp(mock); final MessageSearchReply reply = await api.search(MessageSearchRequest()..query = 'foo'); expect(mock.log, ['search']); @@ -64,7 +64,7 @@ void main() { test('no-arg calls', () async { final MessageApi api = MessageApi(); final Mock mock = Mock(); - TestHostApi.setup(mock); + TestHostApi.setUp(mock); await api.initialize(); expect(mock.log, ['initialize']); }); @@ -73,7 +73,7 @@ void main() { 'calling methods with null', () async { final Mock mock = Mock(); - TestHostApi.setup(mock); + TestHostApi.setUp(mock); expect( await const BasicMessageChannel( 'dev.flutter.pigeon.pigeon_integration_tests.MessageApi.initialize', diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/null_fields_test.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/null_fields_test.dart index 112317a3895..5192bebc3f1 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/test/null_fields_test.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/null_fields_test.dart @@ -63,16 +63,17 @@ void main() { }); test('test reply decode with values', () { - final NullFieldsSearchReply reply = NullFieldsSearchReply.decode([ - 'result', - 'error', - [1, 2, 3], - [ - 'query', - 1, - ], - NullFieldsSearchReplyType.success.index, - ]); + final NullFieldsSearchReply reply = + NullFieldsSearchReply.decode(NullFieldsSearchReply( + result: 'result', + error: 'error', + indices: [1, 2, 3], + request: NullFieldsSearchRequest( + query: 'query', + identifier: 1, + ), + type: NullFieldsSearchReplyType.success, + ).encode()); expect(reply.result, 'result'); expect(reply.error, 'error'); @@ -118,11 +119,13 @@ void main() { }); test('test reply encode with values', () { + final NullFieldsSearchRequest request = + NullFieldsSearchRequest(query: 'query', identifier: 1); final NullFieldsSearchReply reply = NullFieldsSearchReply( result: 'result', error: 'error', indices: [1, 2, 3], - request: NullFieldsSearchRequest(query: 'query', identifier: 1), + request: request, type: NullFieldsSearchReplyType.success, ); @@ -130,11 +133,8 @@ void main() { 'result', 'error', [1, 2, 3], - [ - 'query', - 1, - ], - NullFieldsSearchReplyType.success.index, + request, + NullFieldsSearchReplyType.success, ]); }); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/test/test_message.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/test/test_message.gen.dart index ff3a3d9c89a..27add325337 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/test/test_message.gen.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/test/test_message.gen.dart @@ -4,7 +4,7 @@ // // Autogenerated from Pigeon, do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -14,16 +14,22 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:shared_test_plugin_code/src/generated/message.gen.dart'; -class _TestHostApiCodec extends StandardMessageCodec { - const _TestHostApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is MessageSearchReply) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchRequest) { + if (value is MessageSearchRequest) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is MessageSearchReply) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is MessageNested) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is MessageRequestState) { + buffer.putUint8(132); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -32,10 +38,15 @@ class _TestHostApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return MessageSearchReply.decode(readValue(buffer)!); case 129: return MessageSearchRequest.decode(readValue(buffer)!); + case 130: + return MessageSearchReply.decode(readValue(buffer)!); + case 131: + return MessageNested.decode(readValue(buffer)!); + case 132: + final int? value = readValue(buffer) as int?; + return value == null ? null : MessageRequestState.values[value]; default: return super.readValueOfType(type, buffer); } @@ -48,7 +59,7 @@ class _TestHostApiCodec extends StandardMessageCodec { abstract class TestHostApi { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = _TestHostApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); /// This comment is to test documentation comments. /// @@ -58,36 +69,50 @@ abstract class TestHostApi { /// This comment is to test method documentation comments. MessageSearchReply search(MessageSearchRequest request); - static void setup(TestHostApi? api, {BinaryMessenger? binaryMessenger}) { + static void setUp( + TestHostApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.pigeon_integration_tests.MessageApi.initialize', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.MessageApi.initialize$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_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())); + } }); } } { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.pigeon_integration_tests.MessageApi.search', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.MessageApi.search$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.MessageApi.search was null.'); @@ -96,70 +121,51 @@ abstract class TestHostApi { (args[0] as MessageSearchRequest?); assert(arg_request != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.MessageApi.search was null, expected non-null MessageSearchRequest.'); - final MessageSearchReply output = api.search(arg_request!); - return [output]; + try { + final MessageSearchReply output = api.search(arg_request!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } }); } } } } -class _TestNestedApiCodec extends StandardMessageCodec { - const _TestNestedApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is MessageNested) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchReply) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is MessageSearchRequest) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return MessageNested.decode(readValue(buffer)!); - case 129: - return MessageSearchReply.decode(readValue(buffer)!); - case 130: - return MessageSearchRequest.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - /// This comment is to test api documentation comments. abstract class TestNestedApi { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec codec = _TestNestedApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); /// This comment is to test method documentation comments. /// /// This comment also tests multiple line comments. MessageSearchReply search(MessageNested nested); - static void setup(TestNestedApi? api, {BinaryMessenger? binaryMessenger}) { + static void setUp( + TestNestedApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.pigeon_integration_tests.MessageNestedApi.search', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.pigeon_integration_tests.MessageNestedApi.search$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, + .setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.MessageNestedApi.search was null.'); @@ -167,8 +173,15 @@ abstract class TestNestedApi { final MessageNested? arg_nested = (args[0] as MessageNested?); assert(arg_nested != null, 'Argument for dev.flutter.pigeon.pigeon_integration_tests.MessageNestedApi.search was null, expected non-null MessageNested.'); - final MessageSearchReply output = api.search(arg_nested!); - return [output]; + try { + final MessageSearchReply output = api.search(arg_nested!); + 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/pigeon/platform_tests/test_plugin/android/build.gradle b/packages/pigeon/platform_tests/test_plugin/android/build.gradle index 96cc166d5a8..e99686da09c 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.22' + ext.kotlin_version = '2.0.0' repositories { google() mavenCentral() diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt index 2d462d760cc..f90b5119611 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt @@ -4,6 +4,7 @@ // // Autogenerated from Pigeon, do not edit directly. // See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") package com.example.test_plugin @@ -20,10 +21,10 @@ private fun wrapResult(result: Any?): List { } private fun wrapError(exception: Throwable): List { - if (exception is FlutterError) { - return listOf(exception.code, exception.message, exception.details) + return if (exception is FlutterError) { + listOf(exception.code, exception.message, exception.details) } else { - return listOf( + listOf( exception.javaClass.simpleName, exception.toString(), "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) @@ -63,28 +64,36 @@ data class AllTypes( val a4ByteArray: IntArray, val a8ByteArray: LongArray, val aFloatArray: DoubleArray, - val aList: List, - val aMap: Map, val anEnum: AnEnum, val aString: String, - val anObject: Any + val anObject: Any, + val list: List, + val stringList: List, + val intList: List, + val doubleList: List, + val boolList: List, + val map: Map ) { companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): AllTypes { - val aBool = list[0] as Boolean - val anInt = list[1].let { if (it is Int) it.toLong() else it as Long } - val anInt64 = list[2].let { if (it is Int) it.toLong() else it as Long } - val aDouble = list[3] as Double - val aByteArray = list[4] as ByteArray - val a4ByteArray = list[5] as IntArray - val a8ByteArray = list[6] as LongArray - val aFloatArray = list[7] as DoubleArray - val aList = list[8] as List - val aMap = list[9] as Map - val anEnum = AnEnum.ofRaw(list[10] as Int)!! - val aString = list[11] as String - val anObject = list[12] as Any + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): AllTypes { + val aBool = __pigeon_list[0] as Boolean + val anInt = __pigeon_list[1].let { num -> if (num is Int) num.toLong() else num as Long } + val anInt64 = __pigeon_list[2].let { num -> if (num is Int) num.toLong() else num as Long } + val aDouble = __pigeon_list[3] as Double + val aByteArray = __pigeon_list[4] as ByteArray + val a4ByteArray = __pigeon_list[5] as IntArray + val a8ByteArray = __pigeon_list[6] as LongArray + val aFloatArray = __pigeon_list[7] as DoubleArray + val anEnum = __pigeon_list[8] as AnEnum + val aString = __pigeon_list[9] as String + val anObject = __pigeon_list[10] as Any + val list = __pigeon_list[11] as List + val stringList = __pigeon_list[12] as List + val intList = __pigeon_list[13] as List + val doubleList = __pigeon_list[14] as List + val boolList = __pigeon_list[15] as List + val map = __pigeon_list[16] as Map return AllTypes( aBool, anInt, @@ -94,16 +103,20 @@ data class AllTypes( a4ByteArray, a8ByteArray, aFloatArray, - aList, - aMap, anEnum, aString, - anObject) + anObject, + list, + stringList, + intList, + doubleList, + boolList, + map) } } fun toList(): List { - return listOf( + return listOf( aBool, anInt, anInt64, @@ -112,11 +125,15 @@ data class AllTypes( a4ByteArray, a8ByteArray, aFloatArray, - aList, - aMap, - anEnum.raw, + anEnum, aString, anObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ) } } @@ -135,37 +152,48 @@ data class AllNullableTypes( val aNullable4ByteArray: IntArray? = null, val aNullable8ByteArray: LongArray? = null, val aNullableFloatArray: DoubleArray? = null, - val aNullableList: List? = null, - val aNullableMap: Map? = null, val nullableNestedList: List?>? = null, val nullableMapWithAnnotations: Map? = null, val nullableMapWithObject: Map? = null, val aNullableEnum: AnEnum? = null, val aNullableString: String? = null, val aNullableObject: Any? = null, - val allNullableTypes: AllNullableTypes? = null + val allNullableTypes: AllNullableTypes? = null, + val list: List? = null, + val stringList: List? = null, + val intList: List? = null, + val doubleList: List? = null, + val boolList: List? = null, + val nestedClassList: List? = null, + val map: Map? = null ) { companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): AllNullableTypes { - val aNullableBool = list[0] as Boolean? - val aNullableInt = list[1].let { if (it is Int) it.toLong() else it as Long? } - val aNullableInt64 = list[2].let { if (it is Int) it.toLong() else it as Long? } - val aNullableDouble = list[3] as Double? - val aNullableByteArray = list[4] as ByteArray? - val aNullable4ByteArray = list[5] as IntArray? - val aNullable8ByteArray = list[6] as LongArray? - val aNullableFloatArray = list[7] as DoubleArray? - val aNullableList = list[8] as List? - val aNullableMap = list[9] as Map? - val nullableNestedList = list[10] as List?>? - val nullableMapWithAnnotations = list[11] as Map? - val nullableMapWithObject = list[12] as Map? - val aNullableEnum: AnEnum? = (list[13] as Int?)?.let { AnEnum.ofRaw(it) } - val aNullableString = list[14] as String? - val aNullableObject = list[15] - val allNullableTypes: AllNullableTypes? = - (list[16] as List?)?.let { AllNullableTypes.fromList(it) } + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): AllNullableTypes { + val aNullableBool = __pigeon_list[0] as Boolean? + val aNullableInt = + __pigeon_list[1].let { num -> if (num is Int) num.toLong() else num as Long? } + val aNullableInt64 = + __pigeon_list[2].let { num -> if (num is Int) num.toLong() else num as Long? } + val aNullableDouble = __pigeon_list[3] as Double? + val aNullableByteArray = __pigeon_list[4] as ByteArray? + val aNullable4ByteArray = __pigeon_list[5] as IntArray? + val aNullable8ByteArray = __pigeon_list[6] as LongArray? + val aNullableFloatArray = __pigeon_list[7] as DoubleArray? + val nullableNestedList = __pigeon_list[8] as List?>? + val nullableMapWithAnnotations = __pigeon_list[9] as Map? + val nullableMapWithObject = __pigeon_list[10] as Map? + val aNullableEnum = __pigeon_list[11] as AnEnum? + val aNullableString = __pigeon_list[12] as String? + val aNullableObject = __pigeon_list[13] + val allNullableTypes = __pigeon_list[14] as AllNullableTypes? + val list = __pigeon_list[15] as List? + val stringList = __pigeon_list[16] as List? + val intList = __pigeon_list[17] as List? + val doubleList = __pigeon_list[18] as List? + val boolList = __pigeon_list[19] as List? + val nestedClassList = __pigeon_list[20] as List? + val map = __pigeon_list[21] as Map? return AllNullableTypes( aNullableBool, aNullableInt, @@ -175,20 +203,25 @@ data class AllNullableTypes( aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, aNullableEnum, aNullableString, aNullableObject, - allNullableTypes) + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + nestedClassList, + map) } } fun toList(): List { - return listOf( + return listOf( aNullableBool, aNullableInt, aNullableInt64, @@ -197,15 +230,20 @@ data class AllNullableTypes( aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.raw, + aNullableEnum, aNullableString, aNullableObject, - allNullableTypes?.toList(), + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + nestedClassList, + map, ) } } @@ -225,34 +263,44 @@ data class AllNullableTypesWithoutRecursion( val aNullable4ByteArray: IntArray? = null, val aNullable8ByteArray: LongArray? = null, val aNullableFloatArray: DoubleArray? = null, - val aNullableList: List? = null, - val aNullableMap: Map? = null, val nullableNestedList: List?>? = null, val nullableMapWithAnnotations: Map? = null, val nullableMapWithObject: Map? = null, val aNullableEnum: AnEnum? = null, val aNullableString: String? = null, - val aNullableObject: Any? = null + val aNullableObject: Any? = null, + val list: List? = null, + val stringList: List? = null, + val intList: List? = null, + val doubleList: List? = null, + val boolList: List? = null, + val map: Map? = null ) { companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): AllNullableTypesWithoutRecursion { - val aNullableBool = list[0] as Boolean? - val aNullableInt = list[1].let { if (it is Int) it.toLong() else it as Long? } - val aNullableInt64 = list[2].let { if (it is Int) it.toLong() else it as Long? } - val aNullableDouble = list[3] as Double? - val aNullableByteArray = list[4] as ByteArray? - val aNullable4ByteArray = list[5] as IntArray? - val aNullable8ByteArray = list[6] as LongArray? - val aNullableFloatArray = list[7] as DoubleArray? - val aNullableList = list[8] as List? - val aNullableMap = list[9] as Map? - val nullableNestedList = list[10] as List?>? - val nullableMapWithAnnotations = list[11] as Map? - val nullableMapWithObject = list[12] as Map? - val aNullableEnum: AnEnum? = (list[13] as Int?)?.let { AnEnum.ofRaw(it) } - val aNullableString = list[14] as String? - val aNullableObject = list[15] + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): AllNullableTypesWithoutRecursion { + val aNullableBool = __pigeon_list[0] as Boolean? + val aNullableInt = + __pigeon_list[1].let { num -> if (num is Int) num.toLong() else num as Long? } + val aNullableInt64 = + __pigeon_list[2].let { num -> if (num is Int) num.toLong() else num as Long? } + val aNullableDouble = __pigeon_list[3] as Double? + val aNullableByteArray = __pigeon_list[4] as ByteArray? + val aNullable4ByteArray = __pigeon_list[5] as IntArray? + val aNullable8ByteArray = __pigeon_list[6] as LongArray? + val aNullableFloatArray = __pigeon_list[7] as DoubleArray? + val nullableNestedList = __pigeon_list[8] as List?>? + val nullableMapWithAnnotations = __pigeon_list[9] as Map? + val nullableMapWithObject = __pigeon_list[10] as Map? + val aNullableEnum = __pigeon_list[11] as AnEnum? + val aNullableString = __pigeon_list[12] as String? + val aNullableObject = __pigeon_list[13] + val list = __pigeon_list[14] as List? + val stringList = __pigeon_list[15] as List? + val intList = __pigeon_list[16] as List? + val doubleList = __pigeon_list[17] as List? + val boolList = __pigeon_list[18] as List? + val map = __pigeon_list[19] as Map? return AllNullableTypesWithoutRecursion( aNullableBool, aNullableInt, @@ -262,19 +310,23 @@ data class AllNullableTypesWithoutRecursion( aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, aNullableEnum, aNullableString, - aNullableObject) + aNullableObject, + list, + stringList, + intList, + doubleList, + boolList, + map) } } fun toList(): List { - return listOf( + return listOf( aNullableBool, aNullableInt, aNullableInt64, @@ -283,14 +335,18 @@ data class AllNullableTypesWithoutRecursion( aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.raw, + aNullableEnum, aNullableString, aNullableObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ) } } @@ -310,21 +366,20 @@ data class AllClassesWrapper( val allTypes: AllTypes? = null ) { companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): AllClassesWrapper { - val allNullableTypes = AllNullableTypes.fromList(list[0] as List) - val allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = - (list[1] as List?)?.let { AllNullableTypesWithoutRecursion.fromList(it) } - val allTypes: AllTypes? = (list[2] as List?)?.let { AllTypes.fromList(it) } + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): AllClassesWrapper { + val allNullableTypes = __pigeon_list[0] as AllNullableTypes + val allNullableTypesWithoutRecursion = __pigeon_list[1] as AllNullableTypesWithoutRecursion? + val allTypes = __pigeon_list[2] as AllTypes? return AllClassesWrapper(allNullableTypes, allNullableTypesWithoutRecursion, allTypes) } } fun toList(): List { - return listOf( - allNullableTypes.toList(), - allNullableTypesWithoutRecursion?.toList(), - allTypes?.toList(), + return listOf( + allNullableTypes, + allNullableTypesWithoutRecursion, + allTypes, ) } } @@ -337,67 +392,73 @@ data class AllClassesWrapper( data class TestMessage(val testList: List? = null) { companion object { - @Suppress("UNCHECKED_CAST") - fun fromList(list: List): TestMessage { - val testList = list[0] as List? + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): TestMessage { + val testList = __pigeon_list[0] as List? return TestMessage(testList) } } fun toList(): List { - return listOf( + return listOf( testList, ) } } -@Suppress("UNCHECKED_CAST") -private object HostIntegrationCoreApiCodec : StandardMessageCodec() { +private object CoreTestsPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { - 128.toByte() -> { - return (readValue(buffer) as? List)?.let { AllClassesWrapper.fromList(it) } - } 129.toByte() -> { - return (readValue(buffer) as? List)?.let { AllNullableTypes.fromList(it) } + return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } } 130.toByte() -> { + return (readValue(buffer) as? List)?.let { AllNullableTypes.fromList(it) } + } + 131.toByte() -> { return (readValue(buffer) as? List)?.let { AllNullableTypesWithoutRecursion.fromList(it) } } - 131.toByte() -> { - return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } - } 132.toByte() -> { + return (readValue(buffer) as? List)?.let { AllClassesWrapper.fromList(it) } + } + 133.toByte() -> { return (readValue(buffer) as? List)?.let { TestMessage.fromList(it) } } + 134.toByte() -> { + return (readValue(buffer) as Int?)?.let { AnEnum.ofRaw(it) } + } else -> super.readValueOfType(type, buffer) } } override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { when (value) { - is AllClassesWrapper -> { - stream.write(128) + is AllTypes -> { + stream.write(129) writeValue(stream, value.toList()) } is AllNullableTypes -> { - stream.write(129) + stream.write(130) writeValue(stream, value.toList()) } is AllNullableTypesWithoutRecursion -> { - stream.write(130) + stream.write(131) writeValue(stream, value.toList()) } - is AllTypes -> { - stream.write(131) + is AllClassesWrapper -> { + stream.write(132) writeValue(stream, value.toList()) } is TestMessage -> { - stream.write(132) + stream.write(133) writeValue(stream, value.toList()) } + is AnEnum -> { + stream.write(134) + writeValue(stream, value.raw) + } else -> super.writeValue(stream, value) } } @@ -433,7 +494,7 @@ interface HostIntegrationCoreApi { /** Returns the passed in generic Object. */ fun echoObject(anObject: Any): Any /** Returns the passed list, to test serialization and deserialization. */ - fun echoList(aList: List): List + fun echoList(list: List): List /** Returns the passed map, to test serialization and deserialization. */ fun echoMap(aMap: Map): Map /** Returns the passed map to test nested class serialization and deserialization. */ @@ -512,7 +573,7 @@ interface HostIntegrationCoreApi { /** Returns the passed in generic Object asynchronously. */ fun echoAsyncObject(anObject: Any, callback: (Result) -> Unit) /** Returns the passed list, to test asynchronous serialization and deserialization. */ - fun echoAsyncList(aList: List, callback: (Result>) -> Unit) + fun echoAsyncList(list: List, callback: (Result>) -> Unit) /** Returns the passed map, to test asynchronous serialization and deserialization. */ fun echoAsyncMap(aMap: Map, callback: (Result>) -> Unit) /** Returns the passed enum, to test asynchronous serialization and deserialization. */ @@ -548,7 +609,7 @@ interface HostIntegrationCoreApi { /** Returns the passed in generic Object asynchronously. */ fun echoAsyncNullableObject(anObject: Any?, callback: (Result) -> Unit) /** Returns the passed list, to test asynchronous serialization and deserialization. */ - fun echoAsyncNullableList(aList: List?, callback: (Result?>) -> Unit) + fun echoAsyncNullableList(list: List?, callback: (Result?>) -> Unit) /** Returns the passed map, to test asynchronous serialization and deserialization. */ fun echoAsyncNullableMap( aMap: Map?, @@ -597,9 +658,9 @@ interface HostIntegrationCoreApi { fun callFlutterEchoString(aString: String, callback: (Result) -> Unit) - fun callFlutterEchoUint8List(aList: ByteArray, callback: (Result) -> Unit) + fun callFlutterEchoUint8List(list: ByteArray, callback: (Result) -> Unit) - fun callFlutterEchoList(aList: List, callback: (Result>) -> Unit) + fun callFlutterEchoList(list: List, callback: (Result>) -> Unit) fun callFlutterEchoMap(aMap: Map, callback: (Result>) -> Unit) @@ -613,9 +674,9 @@ interface HostIntegrationCoreApi { fun callFlutterEchoNullableString(aString: String?, callback: (Result) -> Unit) - fun callFlutterEchoNullableUint8List(aList: ByteArray?, callback: (Result) -> Unit) + fun callFlutterEchoNullableUint8List(list: ByteArray?, callback: (Result) -> Unit) - fun callFlutterEchoNullableList(aList: List?, callback: (Result?>) -> Unit) + fun callFlutterEchoNullableList(list: List?, callback: (Result?>) -> Unit) fun callFlutterEchoNullableMap( aMap: Map?, @@ -628,12 +689,12 @@ interface HostIntegrationCoreApi { companion object { /** The codec used by HostIntegrationCoreApi. */ - val codec: MessageCodec by lazy { HostIntegrationCoreApiCodec } + val codec: MessageCodec by lazy { CoreTestsPigeonCodec } /** * Sets up an instance of `HostIntegrationCoreApi` to handle messages through the * `binaryMessenger`. */ - @Suppress("UNCHECKED_CAST") + @JvmOverloads fun setUp( binaryMessenger: BinaryMessenger, api: HostIntegrationCoreApi?, @@ -649,13 +710,13 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - var wrapped: List - try { - api.noop() - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + api.noop() + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -672,12 +733,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val everythingArg = args[0] as AllTypes - var wrapped: List - try { - wrapped = listOf(api.echoAllTypes(everythingArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoAllTypes(everythingArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -692,12 +753,12 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - var wrapped: List - try { - wrapped = listOf(api.throwError()) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.throwError()) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -712,13 +773,13 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - var wrapped: List - try { - api.throwErrorFromVoid() - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + api.throwErrorFromVoid() + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -733,12 +794,12 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - var wrapped: List - try { - wrapped = listOf(api.throwFlutterError()) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.throwFlutterError()) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -754,13 +815,13 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anIntArg = args[0].let { if (it is Int) it.toLong() else it as Long } - var wrapped: List - try { - wrapped = listOf(api.echoInt(anIntArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val anIntArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + listOf(api.echoInt(anIntArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -777,12 +838,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aDoubleArg = args[0] as Double - var wrapped: List - try { - wrapped = listOf(api.echoDouble(aDoubleArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoDouble(aDoubleArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -799,12 +860,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aBoolArg = args[0] as Boolean - var wrapped: List - try { - wrapped = listOf(api.echoBool(aBoolArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoBool(aBoolArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -821,12 +882,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aStringArg = args[0] as String - var wrapped: List - try { - wrapped = listOf(api.echoString(aStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoString(aStringArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -843,12 +904,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aUint8ListArg = args[0] as ByteArray - var wrapped: List - try { - wrapped = listOf(api.echoUint8List(aUint8ListArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoUint8List(aUint8ListArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -865,12 +926,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val anObjectArg = args[0] as Any - var wrapped: List - try { - wrapped = listOf(api.echoObject(anObjectArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoObject(anObjectArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -886,13 +947,13 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as List - var wrapped: List - try { - wrapped = listOf(api.echoList(aListArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val listArg = args[0] as List + val wrapped: List = + try { + listOf(api.echoList(listArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -909,12 +970,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aMapArg = args[0] as Map - var wrapped: List - try { - wrapped = listOf(api.echoMap(aMapArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoMap(aMapArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -931,12 +992,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val wrapperArg = args[0] as AllClassesWrapper - var wrapped: List - try { - wrapped = listOf(api.echoClassWrapper(wrapperArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoClassWrapper(wrapperArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -952,13 +1013,13 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anEnumArg = AnEnum.ofRaw(args[0] as Int)!! - var wrapped: List - try { - wrapped = listOf(api.echoEnum(anEnumArg).raw) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val anEnumArg = args[0] as AnEnum + val wrapped: List = + try { + listOf(api.echoEnum(anEnumArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -975,12 +1036,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aStringArg = args[0] as String - var wrapped: List - try { - wrapped = listOf(api.echoNamedDefaultString(aStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNamedDefaultString(aStringArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -997,12 +1058,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aDoubleArg = args[0] as Double - var wrapped: List - try { - wrapped = listOf(api.echoOptionalDefaultDouble(aDoubleArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoOptionalDefaultDouble(aDoubleArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1018,13 +1079,13 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anIntArg = args[0].let { if (it is Int) it.toLong() else it as Long } - var wrapped: List - try { - wrapped = listOf(api.echoRequiredInt(anIntArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val anIntArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long } + val wrapped: List = + try { + listOf(api.echoRequiredInt(anIntArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1041,12 +1102,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val everythingArg = args[0] as AllNullableTypes? - var wrapped: List - try { - wrapped = listOf(api.echoAllNullableTypes(everythingArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoAllNullableTypes(everythingArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1063,12 +1124,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val everythingArg = args[0] as AllNullableTypesWithoutRecursion? - var wrapped: List - try { - wrapped = listOf(api.echoAllNullableTypesWithoutRecursion(everythingArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoAllNullableTypesWithoutRecursion(everythingArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1085,12 +1146,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val wrapperArg = args[0] as AllClassesWrapper - var wrapped: List - try { - wrapped = listOf(api.extractNestedNullableString(wrapperArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.extractNestedNullableString(wrapperArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1107,12 +1168,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val nullableStringArg = args[0] as String? - var wrapped: List - try { - wrapped = listOf(api.createNestedNullableString(nullableStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.createNestedNullableString(nullableStringArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1129,17 +1190,17 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableBoolArg = args[0] as Boolean? - val aNullableIntArg = args[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableIntArg = + args[1].let { num -> if (num is Int) num.toLong() else num as Long? } val aNullableStringArg = args[2] as String? - var wrapped: List - try { - wrapped = - listOf( + val wrapped: List = + try { + listOf( api.sendMultipleNullableTypes( aNullableBoolArg, aNullableIntArg, aNullableStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1156,17 +1217,17 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableBoolArg = args[0] as Boolean? - val aNullableIntArg = args[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableIntArg = + args[1].let { num -> if (num is Int) num.toLong() else num as Long? } val aNullableStringArg = args[2] as String? - var wrapped: List - try { - wrapped = - listOf( + val wrapped: List = + try { + listOf( api.sendMultipleNullableTypesWithoutRecursion( aNullableBoolArg, aNullableIntArg, aNullableStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1182,13 +1243,14 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aNullableIntArg = args[0].let { if (it is Int) it.toLong() else it as Long? } - var wrapped: List - try { - wrapped = listOf(api.echoNullableInt(aNullableIntArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val aNullableIntArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long? } + val wrapped: List = + try { + listOf(api.echoNullableInt(aNullableIntArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1205,12 +1267,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableDoubleArg = args[0] as Double? - var wrapped: List - try { - wrapped = listOf(api.echoNullableDouble(aNullableDoubleArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableDouble(aNullableDoubleArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1227,12 +1289,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableBoolArg = args[0] as Boolean? - var wrapped: List - try { - wrapped = listOf(api.echoNullableBool(aNullableBoolArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableBool(aNullableBoolArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1249,12 +1311,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableStringArg = args[0] as String? - var wrapped: List - try { - wrapped = listOf(api.echoNullableString(aNullableStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableString(aNullableStringArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1271,12 +1333,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableUint8ListArg = args[0] as ByteArray? - var wrapped: List - try { - wrapped = listOf(api.echoNullableUint8List(aNullableUint8ListArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableUint8List(aNullableUint8ListArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1293,12 +1355,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableObjectArg = args[0] - var wrapped: List - try { - wrapped = listOf(api.echoNullableObject(aNullableObjectArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableObject(aNullableObjectArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1315,12 +1377,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableListArg = args[0] as List? - var wrapped: List - try { - wrapped = listOf(api.echoNullableList(aNullableListArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableList(aNullableListArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1337,12 +1399,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableMapArg = args[0] as Map? - var wrapped: List - try { - wrapped = listOf(api.echoNullableMap(aNullableMapArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNullableMap(aNullableMapArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1358,13 +1420,13 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anEnumArg = if (args[0] == null) null else AnEnum.ofRaw(args[0] as Int) - var wrapped: List - try { - wrapped = listOf(api.echoNullableEnum(anEnumArg)?.raw) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val anEnumArg = args[0] as AnEnum? + val wrapped: List = + try { + listOf(api.echoNullableEnum(anEnumArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1380,13 +1442,14 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aNullableIntArg = args[0].let { if (it is Int) it.toLong() else it as Long? } - var wrapped: List - try { - wrapped = listOf(api.echoOptionalNullableInt(aNullableIntArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val aNullableIntArg = + args[0].let { num -> if (num is Int) num.toLong() else num as Long? } + val wrapped: List = + try { + listOf(api.echoOptionalNullableInt(aNullableIntArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1403,12 +1466,12 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableStringArg = args[0] as String? - var wrapped: List - try { - wrapped = listOf(api.echoNamedNullableString(aNullableStringArg)) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + listOf(api.echoNamedNullableString(aNullableStringArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1423,7 +1486,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.noopAsync() { result: Result -> + api.noopAsync { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -1445,7 +1508,7 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anIntArg = args[0].let { if (it is Int) it.toLong() else it as Long } + val anIntArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long } api.echoAsyncInt(anIntArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { @@ -1589,8 +1652,8 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as List - api.echoAsyncList(aListArg) { result: Result> -> + val listArg = args[0] as List + api.echoAsyncList(listArg) { result: Result> -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -1637,14 +1700,14 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anEnumArg = AnEnum.ofRaw(args[0] as Int)!! + val anEnumArg = args[0] as AnEnum api.echoAsyncEnum(anEnumArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) } else { val data = result.getOrNull() - reply.reply(wrapResult(data!!.raw)) + reply.reply(wrapResult(data)) } } } @@ -1660,7 +1723,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.throwAsyncError() { result: Result -> + api.throwAsyncError { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -1682,7 +1745,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.throwAsyncErrorFromVoid() { result: Result -> + api.throwAsyncErrorFromVoid { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -1703,7 +1766,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.throwAsyncFlutterError() { result: Result -> + api.throwAsyncFlutterError { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -1800,7 +1863,7 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anIntArg = args[0].let { if (it is Int) it.toLong() else it as Long? } + val anIntArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long? } api.echoAsyncNullableInt(anIntArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { @@ -1944,8 +2007,8 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as List? - api.echoAsyncNullableList(aListArg) { result: Result?> -> + val listArg = args[0] as List? + api.echoAsyncNullableList(listArg) { result: Result?> -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -1992,14 +2055,14 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anEnumArg = if (args[0] == null) null else AnEnum.ofRaw(args[0] as Int) + val anEnumArg = args[0] as AnEnum? api.echoAsyncNullableEnum(anEnumArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) } else { val data = result.getOrNull() - reply.reply(wrapResult(data?.raw)) + reply.reply(wrapResult(data)) } } } @@ -2015,7 +2078,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.callFlutterNoop() { result: Result -> + api.callFlutterNoop { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2036,7 +2099,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.callFlutterThrowError() { result: Result -> + api.callFlutterThrowError { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2058,7 +2121,7 @@ interface HostIntegrationCoreApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.callFlutterThrowErrorFromVoid() { result: Result -> + api.callFlutterThrowErrorFromVoid { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2130,7 +2193,8 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableBoolArg = args[0] as Boolean? - val aNullableIntArg = args[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableIntArg = + args[1].let { num -> if (num is Int) num.toLong() else num as Long? } val aNullableStringArg = args[2] as String? api.callFlutterSendMultipleNullableTypes( aNullableBoolArg, aNullableIntArg, aNullableStringArg) { @@ -2183,7 +2247,8 @@ interface HostIntegrationCoreApi { channel.setMessageHandler { message, reply -> val args = message as List val aNullableBoolArg = args[0] as Boolean? - val aNullableIntArg = args[1].let { if (it is Int) it.toLong() else it as Long? } + val aNullableIntArg = + args[1].let { num -> if (num is Int) num.toLong() else num as Long? } val aNullableStringArg = args[2] as String? api.callFlutterSendMultipleNullableTypesWithoutRecursion( aNullableBoolArg, aNullableIntArg, aNullableStringArg) { @@ -2234,7 +2299,7 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anIntArg = args[0].let { if (it is Int) it.toLong() else it as Long } + val anIntArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long } api.callFlutterEchoInt(anIntArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { @@ -2306,8 +2371,8 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as ByteArray - api.callFlutterEchoUint8List(aListArg) { result: Result -> + val listArg = args[0] as ByteArray + api.callFlutterEchoUint8List(listArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2330,8 +2395,8 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as List - api.callFlutterEchoList(aListArg) { result: Result> -> + val listArg = args[0] as List + api.callFlutterEchoList(listArg) { result: Result> -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2378,14 +2443,14 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anEnumArg = AnEnum.ofRaw(args[0] as Int)!! + val anEnumArg = args[0] as AnEnum api.callFlutterEchoEnum(anEnumArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) } else { val data = result.getOrNull() - reply.reply(wrapResult(data!!.raw)) + reply.reply(wrapResult(data)) } } } @@ -2426,7 +2491,7 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anIntArg = args[0].let { if (it is Int) it.toLong() else it as Long? } + val anIntArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long? } api.callFlutterEchoNullableInt(anIntArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { @@ -2498,8 +2563,8 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as ByteArray? - api.callFlutterEchoNullableUint8List(aListArg) { result: Result -> + val listArg = args[0] as ByteArray? + api.callFlutterEchoNullableUint8List(listArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2522,8 +2587,8 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val aListArg = args[0] as List? - api.callFlutterEchoNullableList(aListArg) { result: Result?> -> + val listArg = args[0] as List? + api.callFlutterEchoNullableList(listArg) { result: Result?> -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -2570,14 +2635,14 @@ interface HostIntegrationCoreApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val anEnumArg = if (args[0] == null) null else AnEnum.ofRaw(args[0] as Int) + val anEnumArg = args[0] as AnEnum? api.callFlutterEchoNullableEnum(anEnumArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) } else { val data = result.getOrNull() - reply.reply(wrapResult(data?.raw)) + reply.reply(wrapResult(data)) } } } @@ -2612,73 +2677,19 @@ interface HostIntegrationCoreApi { } } } - -@Suppress("UNCHECKED_CAST") -private object FlutterIntegrationCoreApiCodec : StandardMessageCodec() { - override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return when (type) { - 128.toByte() -> { - return (readValue(buffer) as? List)?.let { AllClassesWrapper.fromList(it) } - } - 129.toByte() -> { - return (readValue(buffer) as? List)?.let { AllNullableTypes.fromList(it) } - } - 130.toByte() -> { - return (readValue(buffer) as? List)?.let { - AllNullableTypesWithoutRecursion.fromList(it) - } - } - 131.toByte() -> { - return (readValue(buffer) as? List)?.let { AllTypes.fromList(it) } - } - 132.toByte() -> { - return (readValue(buffer) as? List)?.let { TestMessage.fromList(it) } - } - else -> super.readValueOfType(type, buffer) - } - } - - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - when (value) { - is AllClassesWrapper -> { - stream.write(128) - writeValue(stream, value.toList()) - } - is AllNullableTypes -> { - stream.write(129) - writeValue(stream, value.toList()) - } - is AllNullableTypesWithoutRecursion -> { - stream.write(130) - writeValue(stream, value.toList()) - } - is AllTypes -> { - stream.write(131) - writeValue(stream, value.toList()) - } - is TestMessage -> { - stream.write(132) - writeValue(stream, value.toList()) - } - else -> super.writeValue(stream, value) - } - } -} - /** * The core interface that the Dart platform_test code implements for host integration tests to call * into. * * Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ -@Suppress("UNCHECKED_CAST") class FlutterIntegrationCoreApi( private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "" ) { companion object { /** The codec used by FlutterIntegrationCoreApi. */ - val codec: MessageCodec by lazy { FlutterIntegrationCoreApiCodec } + val codec: MessageCodec by lazy { CoreTestsPigeonCodec } } /** A no-op function taking no arguments and returning no value, to sanity test basic calling. */ fun noop(callback: (Result) -> Unit) { @@ -2929,7 +2940,7 @@ class FlutterIntegrationCoreApi( "Flutter api returned null value for non-null return value.", ""))) } else { - val output = it[0].let { if (it is Int) it.toLong() else it as Long } + val output = it[0].let { num -> if (num is Int) num.toLong() else num as Long } callback(Result.success(output)) } } else { @@ -2992,13 +3003,13 @@ class FlutterIntegrationCoreApi( } } /** Returns the passed byte list, to test serialization and deserialization. */ - fun echoUint8List(aListArg: ByteArray, callback: (Result) -> Unit) { + fun echoUint8List(listArg: ByteArray, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoUint8List$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(aListArg)) { + channel.send(listOf(listArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) @@ -3019,13 +3030,13 @@ class FlutterIntegrationCoreApi( } } /** Returns the passed list, to test serialization and deserialization. */ - fun echoList(aListArg: List, callback: (Result>) -> Unit) { + fun echoList(listArg: List, callback: (Result>) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoList$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(aListArg)) { + channel.send(listOf(listArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) @@ -3079,7 +3090,7 @@ class FlutterIntegrationCoreApi( val channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoEnum$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(anEnumArg.raw)) { + channel.send(listOf(anEnumArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) @@ -3091,7 +3102,7 @@ class FlutterIntegrationCoreApi( "Flutter api returned null value for non-null return value.", ""))) } else { - val output = AnEnum.ofRaw(it[0] as Int)!! + val output = it[0] as AnEnum callback(Result.success(output)) } } else { @@ -3131,7 +3142,7 @@ class FlutterIntegrationCoreApi( if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { - val output = it[0].let { if (it is Int) it.toLong() else it as Long? } + val output = it[0].let { num -> if (num is Int) num.toLong() else num as Long? } callback(Result.success(output)) } } else { @@ -3180,13 +3191,13 @@ class FlutterIntegrationCoreApi( } } /** Returns the passed byte list, to test serialization and deserialization. */ - fun echoNullableUint8List(aListArg: ByteArray?, callback: (Result) -> Unit) { + fun echoNullableUint8List(listArg: ByteArray?, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableUint8List$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(aListArg)) { + channel.send(listOf(listArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) @@ -3200,13 +3211,13 @@ class FlutterIntegrationCoreApi( } } /** Returns the passed list, to test serialization and deserialization. */ - fun echoNullableList(aListArg: List?, callback: (Result?>) -> Unit) { + fun echoNullableList(listArg: List?, callback: (Result?>) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableList$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(aListArg)) { + channel.send(listOf(listArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) @@ -3249,12 +3260,12 @@ class FlutterIntegrationCoreApi( val channelName = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableEnum$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(anEnumArg?.raw)) { + channel.send(listOf(anEnumArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else { - val output = (it[0] as Int?)?.let { AnEnum.ofRaw(it) } + val output = it[0] as AnEnum? callback(Result.success(output)) } } else { @@ -3322,9 +3333,9 @@ interface HostTrivialApi { companion object { /** The codec used by HostTrivialApi. */ - val codec: MessageCodec by lazy { StandardMessageCodec() } + val codec: MessageCodec by lazy { CoreTestsPigeonCodec } /** Sets up an instance of `HostTrivialApi` to handle messages through the `binaryMessenger`. */ - @Suppress("UNCHECKED_CAST") + @JvmOverloads fun setUp( binaryMessenger: BinaryMessenger, api: HostTrivialApi?, @@ -3340,13 +3351,13 @@ interface HostTrivialApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - var wrapped: List - try { - api.noop() - wrapped = listOf(null) - } catch (exception: Throwable) { - wrapped = wrapError(exception) - } + val wrapped: List = + try { + api.noop() + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -3368,9 +3379,9 @@ interface HostSmallApi { companion object { /** The codec used by HostSmallApi. */ - val codec: MessageCodec by lazy { StandardMessageCodec() } + val codec: MessageCodec by lazy { CoreTestsPigeonCodec } /** Sets up an instance of `HostSmallApi` to handle messages through the `binaryMessenger`. */ - @Suppress("UNCHECKED_CAST") + @JvmOverloads fun setUp( binaryMessenger: BinaryMessenger, api: HostSmallApi?, @@ -3410,7 +3421,7 @@ interface HostSmallApi { codec) if (api != null) { channel.setMessageHandler { _, reply -> - api.voidVoid() { result: Result -> + api.voidVoid { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) @@ -3426,42 +3437,18 @@ interface HostSmallApi { } } } - -@Suppress("UNCHECKED_CAST") -private object FlutterSmallApiCodec : StandardMessageCodec() { - override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return when (type) { - 128.toByte() -> { - return (readValue(buffer) as? List)?.let { TestMessage.fromList(it) } - } - else -> super.readValueOfType(type, buffer) - } - } - - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - when (value) { - is TestMessage -> { - stream.write(128) - writeValue(stream, value.toList()) - } - else -> super.writeValue(stream, value) - } - } -} - /** * A simple API called in some unit tests. * * Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ -@Suppress("UNCHECKED_CAST") class FlutterSmallApi( private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "" ) { companion object { /** The codec used by FlutterSmallApi. */ - val codec: MessageCodec by lazy { FlutterSmallApiCodec } + val codec: MessageCodec by lazy { CoreTestsPigeonCodec } } fun echoWrappedList(msgArg: TestMessage, callback: (Result) -> Unit) { diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt index 8453a1cae2c..43ed1282d12 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt @@ -13,18 +13,18 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { private var flutterSmallApiOne: FlutterSmallApi? = null private var flutterSmallApiTwo: FlutterSmallApi? = null - override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + override fun onAttachedToEngine(binding: FlutterPluginBinding) { HostIntegrationCoreApi.setUp(binding.binaryMessenger, this) - val testSuffixApiOne: TestPluginWithSuffix = TestPluginWithSuffix() + val testSuffixApiOne = TestPluginWithSuffix() testSuffixApiOne.setUp(binding, "suffixOne") - val testSuffixApiTwo: TestPluginWithSuffix = TestPluginWithSuffix() + val testSuffixApiTwo = TestPluginWithSuffix() testSuffixApiTwo.setUp(binding, "suffixTwo") flutterApi = FlutterIntegrationCoreApi(binding.binaryMessenger) flutterSmallApiOne = FlutterSmallApi(binding.binaryMessenger, "suffixOne") flutterSmallApiTwo = FlutterSmallApi(binding.binaryMessenger, "suffixTwo") } - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} + override fun onDetachedFromEngine(binding: FlutterPluginBinding) {} // HostIntegrationCoreApi @@ -80,8 +80,8 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { return anObject } - override fun echoList(aList: List): List { - return aList + override fun echoList(list: List): List { + return list } override fun echoMap(aMap: Map): Map { @@ -240,8 +240,8 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { callback(Result.success(anObject)) } - override fun echoAsyncList(aList: List, callback: (Result>) -> Unit) { - callback(Result.success(aList)) + override fun echoAsyncList(list: List, callback: (Result>) -> Unit) { + callback(Result.success(list)) } override fun echoAsyncMap( @@ -282,8 +282,8 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { callback(Result.success(anObject)) } - override fun echoAsyncNullableList(aList: List?, callback: (Result?>) -> Unit) { - callback(Result.success(aList)) + override fun echoAsyncNullableList(list: List?, callback: (Result?>) -> Unit) { + callback(Result.success(list)) } override fun echoAsyncNullableMap( @@ -298,15 +298,15 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { } override fun callFlutterNoop(callback: (Result) -> Unit) { - flutterApi!!.noop() { callback(Result.success(Unit)) } + flutterApi!!.noop { callback(Result.success(Unit)) } } override fun callFlutterThrowError(callback: (Result) -> Unit) { - flutterApi!!.throwError() { result -> callback(result) } + flutterApi!!.throwError { result -> callback(result) } } override fun callFlutterThrowErrorFromVoid(callback: (Result) -> Unit) { - flutterApi!!.throwErrorFromVoid() { result -> callback(result) } + flutterApi!!.throwErrorFromVoid { result -> callback(result) } } override fun callFlutterEchoAllTypes(everything: AllTypes, callback: (Result) -> Unit) { @@ -359,12 +359,12 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { flutterApi!!.echoString(aString) { echo -> callback(echo) } } - override fun callFlutterEchoUint8List(aList: ByteArray, callback: (Result) -> Unit) { - flutterApi!!.echoUint8List(aList) { echo -> callback(echo) } + override fun callFlutterEchoUint8List(list: ByteArray, callback: (Result) -> Unit) { + flutterApi!!.echoUint8List(list) { echo -> callback(echo) } } - override fun callFlutterEchoList(aList: List, callback: (Result>) -> Unit) { - flutterApi!!.echoList(aList) { echo -> callback(echo) } + override fun callFlutterEchoList(list: List, callback: (Result>) -> Unit) { + flutterApi!!.echoList(list) { echo -> callback(echo) } } override fun callFlutterEchoMap( @@ -375,6 +375,7 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { } override fun callFlutterEchoEnum(anEnum: AnEnum, callback: (Result) -> Unit) { + // callback(Result.success(anEnum)) flutterApi!!.echoEnum(anEnum) { echo -> callback(echo) } } @@ -408,17 +409,17 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { } override fun callFlutterEchoNullableUint8List( - aList: ByteArray?, + list: ByteArray?, callback: (Result) -> Unit ) { - flutterApi!!.echoNullableUint8List(aList) { echo -> callback(echo) } + flutterApi!!.echoNullableUint8List(list) { echo -> callback(echo) } } override fun callFlutterEchoNullableList( - aList: List?, + list: List?, callback: (Result?>) -> Unit ) { - flutterApi!!.echoNullableList(aList) { echo -> callback(echo) } + flutterApi!!.echoNullableList(list) { echo -> callback(echo) } } override fun callFlutterEchoNullableMap( diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt index 81207b03f58..56e0111d63f 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt @@ -9,10 +9,14 @@ import io.mockk.every import io.mockk.mockk import java.nio.ByteBuffer import java.util.ArrayList -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test -internal class AllDatatypesTest : TestCase() { +internal class AllDatatypesTest { + fun compareAllTypes(firstTypes: AllTypes?, secondTypes: AllTypes?) { assertEquals(firstTypes == null, secondTypes == null) if (firstTypes == null || secondTypes == null) { @@ -21,19 +25,26 @@ internal class AllDatatypesTest : TestCase() { assertEquals(firstTypes.aBool, secondTypes.aBool) assertEquals(firstTypes.anInt, secondTypes.anInt) assertEquals(firstTypes.anInt64, secondTypes.anInt64) - assertEquals(firstTypes.aDouble, secondTypes.aDouble) + assertEquals(firstTypes.aDouble, secondTypes.aDouble, 0.0) assertEquals(firstTypes.aString, secondTypes.aString) assertTrue(firstTypes.aByteArray.contentEquals(secondTypes.aByteArray)) assertTrue(firstTypes.a4ByteArray.contentEquals(secondTypes.a4ByteArray)) assertTrue(firstTypes.a8ByteArray.contentEquals(secondTypes.a8ByteArray)) assertTrue(firstTypes.aFloatArray.contentEquals(secondTypes.aFloatArray)) - assertEquals(firstTypes.aList, secondTypes.aList) - assertEquals(firstTypes.aMap, secondTypes.aMap) assertEquals(firstTypes.anEnum, secondTypes.anEnum) assertEquals(firstTypes.anObject, secondTypes.anObject) + assertEquals(firstTypes.list, secondTypes.list) + assertEquals(firstTypes.boolList, secondTypes.boolList) + assertEquals(firstTypes.doubleList, secondTypes.doubleList) + assertEquals(firstTypes.intList, secondTypes.intList) + assertEquals(firstTypes.stringList, secondTypes.stringList) + assertEquals(firstTypes.map, secondTypes.map) } - fun compareAllNullableTypes(firstTypes: AllNullableTypes?, secondTypes: AllNullableTypes?) { + private fun compareAllNullableTypes( + firstTypes: AllNullableTypes?, + secondTypes: AllNullableTypes? + ) { assertEquals(firstTypes == null, secondTypes == null) if (firstTypes == null || secondTypes == null) { return @@ -46,10 +57,14 @@ internal class AllDatatypesTest : TestCase() { assertTrue(firstTypes.aNullable4ByteArray.contentEquals(secondTypes.aNullable4ByteArray)) assertTrue(firstTypes.aNullable8ByteArray.contentEquals(secondTypes.aNullable8ByteArray)) assertTrue(firstTypes.aNullableFloatArray.contentEquals(secondTypes.aNullableFloatArray)) - assertEquals(firstTypes.aNullableList, secondTypes.aNullableList) - assertEquals(firstTypes.aNullableMap, secondTypes.aNullableMap) assertEquals(firstTypes.nullableMapWithObject, secondTypes.nullableMapWithObject) assertEquals(firstTypes.aNullableObject, secondTypes.aNullableObject) + assertEquals(firstTypes.list, secondTypes.list) + assertEquals(firstTypes.boolList, secondTypes.boolList) + assertEquals(firstTypes.doubleList, secondTypes.doubleList) + assertEquals(firstTypes.intList, secondTypes.intList) + assertEquals(firstTypes.stringList, secondTypes.stringList) + assertEquals(firstTypes.map, secondTypes.map) } @Test @@ -71,22 +86,9 @@ internal class AllDatatypesTest : TestCase() { } var didCall = false - api.echoAllNullableTypes(everything) { + api.echoAllNullableTypes(everything) { result -> didCall = true - val output = - (it.getOrNull())?.let { - assertNull(it.aNullableBool) - assertNull(it.aNullableInt) - assertNull(it.aNullableDouble) - assertNull(it.aNullableString) - assertNull(it.aNullableByteArray) - assertNull(it.aNullable4ByteArray) - assertNull(it.aNullable8ByteArray) - assertNull(it.aNullableFloatArray) - assertNull(it.aNullableList) - assertNull(it.aNullableMap) - assertNull(it.nullableMapWithObject) - } + val output = (result.getOrNull())?.let { compareAllNullableTypes(it, everything) } assertNotNull(output) } @@ -105,11 +107,14 @@ internal class AllDatatypesTest : TestCase() { aNullable4ByteArray = intArrayOf(1, 2, 3, 4), aNullable8ByteArray = longArrayOf(1, 2, 3, 4), aNullableFloatArray = doubleArrayOf(0.5, 0.25, 1.5, 1.25), - aNullableList = listOf(1, 2, 3), - aNullableMap = mapOf("hello" to 1234), nullableMapWithObject = mapOf("hello" to 1234), aNullableObject = 0, - ) + list = listOf(1, 2, 3), + stringList = listOf("string", "another one"), + boolList = listOf(true, false), + intList = listOf(1, 2), + doubleList = listOf(1.1, 2.2), + map = mapOf("hello" to 1234)) val binaryMessenger = mockk() val api = FlutterIntegrationCoreApi(binaryMessenger) @@ -161,6 +166,11 @@ internal class AllDatatypesTest : TestCase() { null, null, null, + null, + null, + null, + null, + null, null) val everything2 = AllNullableTypes.fromList(list2) diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AsyncHandlersTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AsyncHandlersTest.kt index d42c62fefcb..8de43ad15fb 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AsyncHandlersTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AsyncHandlersTest.kt @@ -10,10 +10,14 @@ import io.mockk.mockk import io.mockk.slot import io.mockk.verify import java.nio.ByteBuffer -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test -internal class AsyncHandlersTest : TestCase() { +internal class AsyncHandlersTest { + @Test fun testAsyncHost2Flutter() { val binaryMessenger = mockk() @@ -56,7 +60,6 @@ internal class AsyncHandlersTest : TestCase() { val handlerSlot = slot() val input = "Test" - val output = input val channelName = "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo" every { @@ -67,7 +70,7 @@ internal class AsyncHandlersTest : TestCase() { every { api.echo(any(), any()) } answers { val callback = arg<(Result) -> Unit>(1) - callback(Result.success(output)) + callback(Result.success(input)) } HostSmallApi.setUp(binaryMessenger, api) @@ -80,7 +83,7 @@ internal class AsyncHandlersTest : TestCase() { it?.rewind() @Suppress("UNCHECKED_CAST") val wrapped = codec.decodeMessage(it) as MutableList? assertNotNull(wrapped) - wrapped?.let { assertEquals(output, wrapped.first()) } + wrapped?.let { assertEquals(input, wrapped.first()) } } verify { binaryMessenger.setMessageHandler(channelName, handlerSlot.captured) } @@ -88,7 +91,7 @@ internal class AsyncHandlersTest : TestCase() { } @Test - fun asyncFlutter2HostVoidVoid() { + fun testAsyncFlutter2HostVoidVoid() { val binaryMessenger = mockk() val api = mockk() diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/EnumTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/EnumTest.kt index 1117a8a3a1e..acdc6192c01 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/EnumTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/EnumTest.kt @@ -11,10 +11,13 @@ import io.mockk.slot import io.mockk.verify import java.nio.ByteBuffer import java.util.ArrayList -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test -internal class EnumTest : TestCase() { +internal class EnumTest { + @Test fun testEchoHost() { val binaryMessenger = mockk() diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/ListTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/ListTest.kt index 923cc3b90e6..cab26d56a52 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/ListTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/ListTest.kt @@ -9,10 +9,12 @@ import io.mockk.every import io.mockk.mockk import java.nio.ByteBuffer import java.util.ArrayList -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test -class ListTest : TestCase() { +class ListTest { + @Test fun testListInList() { val binaryMessenger = mockk() diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/MultipleArityTests.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/MultipleArityTests.kt index 27925a308ae..789c944eb48 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/MultipleArityTests.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/MultipleArityTests.kt @@ -10,10 +10,13 @@ import io.mockk.mockk import io.mockk.slot import java.nio.ByteBuffer import java.util.ArrayList -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test -class MultipleArityTests : TestCase() { +class MultipleArityTests { + @Test fun testSimpleHost() { val binaryMessenger = mockk() diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NonNullFieldsTests.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NonNullFieldsTests.kt index c5357af1b72..6e867760a08 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NonNullFieldsTests.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NonNullFieldsTests.kt @@ -4,10 +4,11 @@ package com.example.test_plugin -import junit.framework.TestCase +import org.junit.Assert.assertEquals import org.junit.Test -class NonNullFieldsTests : TestCase() { +class NonNullFieldsTests { + @Test fun testMake() { val request = NonNullFieldSearchRequest("hello") diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NullableReturnsTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NullableReturnsTest.kt index a1f6b52558b..78c85ba91d8 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NullableReturnsTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/NullableReturnsTest.kt @@ -9,10 +9,13 @@ import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test -class NullableReturnsTest : TestCase() { +class NullableReturnsTest { + @Test fun testNullableParameterHost() { val binaryMessenger = mockk(relaxed = true) diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/PrimitiveTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/PrimitiveTest.kt index a883b6b0325..047c5b3c219 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/PrimitiveTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/PrimitiveTest.kt @@ -9,10 +9,13 @@ import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify -import junit.framework.TestCase +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test -class PrimitiveTest : TestCase() { +class PrimitiveTest { + @Test fun testIntPrimitiveHost() { val binaryMessenger = mockk(relaxed = true) diff --git a/packages/pigeon/platform_tests/test_plugin/example/android/.gitignore b/packages/pigeon/platform_tests/test_plugin/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/android/.gitignore +++ b/packages/pigeon/platform_tests/test_plugin/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/pigeon/platform_tests/test_plugin/example/android/build.gradle b/packages/pigeon/platform_tests/test_plugin/example/android/build.gradle index 26e1ce2d030..14cf5d6d58d 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/android/build.gradle +++ b/packages/pigeon/platform_tests/test_plugin/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/pigeon/platform_tests/test_plugin/example/android/settings.gradle b/packages/pigeon/platform_tests/test_plugin/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/android/settings.gradle +++ b/packages/pigeon/platform_tests/test_plugin/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7e1380f00a5..58761a450d7 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ (codec: FlutterIntegrationCoreApiCodec.shared) + let binaryMessenger = MockBinaryMessenger(codec: CoreTestsPigeonCodec.shared) binaryMessenger.result = value let flutterApi = FlutterIntegrationCoreApi(binaryMessenger: binaryMessenger) diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/EnumTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/EnumTests.swift index 391ace5d914..063fc95cdd8 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/EnumTests.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/EnumTests.swift @@ -12,7 +12,7 @@ class MockEnumApi2Host: EnumApi2Host { } } -extension DataWithEnum: Equatable { +extension test_plugin.DataWithEnum: Swift.Equatable { public static func == (lhs: DataWithEnum, rhs: DataWithEnum) -> Bool { lhs.state == rhs.state } @@ -21,7 +21,7 @@ extension DataWithEnum: Equatable { class EnumTests: XCTestCase { func testEchoHost() throws { - let binaryMessenger = MockBinaryMessenger(codec: EnumApi2HostCodec.shared) + let binaryMessenger = MockBinaryMessenger(codec: EnumPigeonCodec.shared) EnumApi2HostSetup.setUp(binaryMessenger: binaryMessenger, api: MockEnumApi2Host()) let channelName = "dev.flutter.pigeon.pigeon_integration_tests.EnumApi2Host.echo" XCTAssertNotNil(binaryMessenger.handlers[channelName]) @@ -44,7 +44,7 @@ class EnumTests: XCTestCase { func testEchoFlutter() throws { let data = DataWithEnum(state: .error) - let binaryMessenger = EchoBinaryMessenger(codec: EnumApi2HostCodec.shared) + let binaryMessenger = EchoBinaryMessenger(codec: EnumPigeonCodec.shared) let api = EnumApi2Flutter(binaryMessenger: binaryMessenger) let expectation = XCTestExpectation(description: "callback") diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/ListTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/ListTests.swift index 4fc7de6b96e..fa56f7231ff 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/ListTests.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/ListTests.swift @@ -11,7 +11,7 @@ class ListTests: XCTestCase { func testListInList() throws { let inside = TestMessage(testList: [1, 2, 3]) let top = TestMessage(testList: [inside]) - let binaryMessenger = EchoBinaryMessenger(codec: FlutterSmallApiCodec.shared) + let binaryMessenger = EchoBinaryMessenger(codec: CoreTestsPigeonCodec.shared) let api = FlutterSmallApi(binaryMessenger: binaryMessenger) let expectation = XCTestExpectation(description: "callback") diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/MultipleArityTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/MultipleArityTests.swift index f42933b2391..6994a6703bf 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/MultipleArityTests.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/MultipleArityTests.swift @@ -16,7 +16,7 @@ class MockMultipleArityHostApi: MultipleArityHostApi { class MultipleArityTests: XCTestCase { var codec = FlutterStandardMessageCodec.sharedInstance() func testSimpleHost() throws { - let binaryMessenger = MockBinaryMessenger(codec: EnumApi2HostCodec.shared) + let binaryMessenger = MockBinaryMessenger(codec: EnumPigeonCodec.shared) MultipleArityHostApiSetup.setUp( binaryMessenger: binaryMessenger, api: MockMultipleArityHostApi()) let channelName = "dev.flutter.pigeon.pigeon_integration_tests.MultipleArityHostApi.subtract" diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/RunnerTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/RunnerTests.swift index e86e5c948d1..4e02aa6e24b 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/RunnerTests.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/RunnerTests.swift @@ -48,14 +48,16 @@ class RunnerTests: XCTestCase { } class FlutterApiFromProtocol: FlutterSmallApiProtocol { - func echo(string aStringArg: String, completion: @escaping (Result) -> Void) - { + func echo( + string aStringArg: String, + completion: @escaping (Result) -> Void + ) { completion(.success(aStringArg)) } func echo( _ msgArg: test_plugin.TestMessage, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { completion(.success(msgArg)) } diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..9f56413f381 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,7 @@ com.apple.security.app-sandbox - + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements index 852fa1a4728..e89b7f323cf 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements @@ -3,6 +3,6 @@ com.apple.security.app-sandbox - + diff --git a/packages/pigeon/platform_tests/test_plugin/example/pubspec.yaml b/packages/pigeon/platform_tests/test_plugin/example/pubspec.yaml index ce9c5c7527a..41f9074af62 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/pubspec.yaml +++ b/packages/pigeon/platform_tests/test_plugin/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Pigeon test harness for primary plugin languages. publish_to: 'none' environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: flutter: diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift index f02daf70167..2b95f040b48 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift @@ -15,11 +15,36 @@ import Foundation #error("Unsupported platform.") #endif +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + private func wrapResult(_ result: Any?) -> [Any?] { return [result] } private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } if let flutterError = error as? FlutterError { return [ flutterError.code, @@ -34,8 +59,8 @@ private func wrapError(_ error: Any) -> [Any?] { ] } -private func createConnectionError(withChannelName channelName: String) -> FlutterError { - return FlutterError( +private func createConnectionError(withChannelName channelName: String) -> PigeonError { + return PigeonError( code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") } @@ -69,26 +94,37 @@ struct AllTypes { var a4ByteArray: FlutterStandardTypedData var a8ByteArray: FlutterStandardTypedData var aFloatArray: FlutterStandardTypedData - var aList: [Any?] - var aMap: [AnyHashable: Any?] var anEnum: AnEnum var aString: String var anObject: Any + var list: [Any?] + var stringList: [String?] + var intList: [Int64?] + var doubleList: [Double?] + var boolList: [Bool?] + var map: [AnyHashable: Any?] - static func fromList(_ list: [Any?]) -> AllTypes? { - let aBool = list[0] as! Bool - let anInt = list[1] is Int64 ? list[1] as! Int64 : Int64(list[1] as! Int32) - let anInt64 = list[2] is Int64 ? list[2] as! Int64 : Int64(list[2] as! Int32) - let aDouble = list[3] as! Double - let aByteArray = list[4] as! FlutterStandardTypedData - let a4ByteArray = list[5] as! FlutterStandardTypedData - let a8ByteArray = list[6] as! FlutterStandardTypedData - let aFloatArray = list[7] as! FlutterStandardTypedData - let aList = list[8] as! [Any?] - let aMap = list[9] as! [AnyHashable: Any?] - let anEnum = AnEnum(rawValue: list[10] as! Int)! - let aString = list[11] as! String - let anObject = list[12]! + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllTypes? { + let aBool = __pigeon_list[0] as! Bool + let anInt = + __pigeon_list[1] is Int64 ? __pigeon_list[1] as! Int64 : Int64(__pigeon_list[1] as! Int32) + let anInt64 = + __pigeon_list[2] is Int64 ? __pigeon_list[2] as! Int64 : Int64(__pigeon_list[2] as! Int32) + let aDouble = __pigeon_list[3] as! Double + let aByteArray = __pigeon_list[4] as! FlutterStandardTypedData + let a4ByteArray = __pigeon_list[5] as! FlutterStandardTypedData + let a8ByteArray = __pigeon_list[6] as! FlutterStandardTypedData + let aFloatArray = __pigeon_list[7] as! FlutterStandardTypedData + let anEnum = __pigeon_list[8] as! AnEnum + let aString = __pigeon_list[9] as! String + let anObject = __pigeon_list[10]! + let list = __pigeon_list[11] as! [Any?] + let stringList = __pigeon_list[12] as! [String?] + let intList = __pigeon_list[13] as! [Int64?] + let doubleList = __pigeon_list[14] as! [Double?] + let boolList = __pigeon_list[15] as! [Bool?] + let map = __pigeon_list[16] as! [AnyHashable: Any?] return AllTypes( aBool: aBool, @@ -99,11 +135,15 @@ struct AllTypes { a4ByteArray: a4ByteArray, a8ByteArray: a8ByteArray, aFloatArray: aFloatArray, - aList: aList, - aMap: aMap, anEnum: anEnum, aString: aString, - anObject: anObject + anObject: anObject, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map ) } func toList() -> [Any?] { @@ -116,11 +156,15 @@ struct AllTypes { a4ByteArray, a8ByteArray, aFloatArray, - aList, - aMap, - anEnum.rawValue, + anEnum, aString, anObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ] } } @@ -138,15 +182,20 @@ class AllNullableTypes { aNullable4ByteArray: FlutterStandardTypedData? = nil, aNullable8ByteArray: FlutterStandardTypedData? = nil, aNullableFloatArray: FlutterStandardTypedData? = nil, - aNullableList: [Any?]? = nil, - aNullableMap: [AnyHashable: Any?]? = nil, nullableNestedList: [[Bool?]?]? = nil, nullableMapWithAnnotations: [String?: String?]? = nil, nullableMapWithObject: [String?: Any?]? = nil, aNullableEnum: AnEnum? = nil, aNullableString: String? = nil, aNullableObject: Any? = nil, - allNullableTypes: AllNullableTypes? = nil + allNullableTypes: AllNullableTypes? = nil, + list: [Any?]? = nil, + stringList: [String?]? = nil, + intList: [Int64?]? = nil, + doubleList: [Double?]? = nil, + boolList: [Bool?]? = nil, + nestedClassList: [AllNullableTypes?]? = nil, + map: [AnyHashable: Any?]? = nil ) { self.aNullableBool = aNullableBool self.aNullableInt = aNullableInt @@ -156,8 +205,6 @@ class AllNullableTypes { self.aNullable4ByteArray = aNullable4ByteArray self.aNullable8ByteArray = aNullable8ByteArray self.aNullableFloatArray = aNullableFloatArray - self.aNullableList = aNullableList - self.aNullableMap = aNullableMap self.nullableNestedList = nullableNestedList self.nullableMapWithAnnotations = nullableMapWithAnnotations self.nullableMapWithObject = nullableMapWithObject @@ -165,6 +212,13 @@ class AllNullableTypes { self.aNullableString = aNullableString self.aNullableObject = aNullableObject self.allNullableTypes = allNullableTypes + self.list = list + self.stringList = stringList + self.intList = intList + self.doubleList = doubleList + self.boolList = boolList + self.nestedClassList = nestedClassList + self.map = map } var aNullableBool: Bool? var aNullableInt: Int64? @@ -174,8 +228,6 @@ class AllNullableTypes { var aNullable4ByteArray: FlutterStandardTypedData? var aNullable8ByteArray: FlutterStandardTypedData? var aNullableFloatArray: FlutterStandardTypedData? - var aNullableList: [Any?]? - var aNullableMap: [AnyHashable: Any?]? var nullableNestedList: [[Bool?]?]? var nullableMapWithAnnotations: [String?: String?]? var nullableMapWithObject: [String?: Any?]? @@ -183,34 +235,46 @@ class AllNullableTypes { var aNullableString: String? var aNullableObject: Any? var allNullableTypes: AllNullableTypes? + var list: [Any?]? + var stringList: [String?]? + var intList: [Int64?]? + var doubleList: [Double?]? + var boolList: [Bool?]? + var nestedClassList: [AllNullableTypes?]? + var map: [AnyHashable: Any?]? - static func fromList(_ list: [Any?]) -> AllNullableTypes? { - let aNullableBool: Bool? = nilOrValue(list[0]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllNullableTypes? { + let aNullableBool: Bool? = nilOrValue(__pigeon_list[0]) let aNullableInt: Int64? = - isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) + isNullish(__pigeon_list[1]) + ? nil + : (__pigeon_list[1] is Int64? + ? __pigeon_list[1] as! Int64? : Int64(__pigeon_list[1] as! Int32)) let aNullableInt64: Int64? = - isNullish(list[2]) ? nil : (list[2] is Int64? ? list[2] as! Int64? : Int64(list[2] as! Int32)) - let aNullableDouble: Double? = nilOrValue(list[3]) - let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(list[4]) - let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(list[5]) - let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(list[6]) - let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(list[7]) - let aNullableList: [Any?]? = nilOrValue(list[8]) - let aNullableMap: [AnyHashable: Any?]? = nilOrValue(list[9]) - let nullableNestedList: [[Bool?]?]? = nilOrValue(list[10]) - let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(list[11]) - let nullableMapWithObject: [String?: Any?]? = nilOrValue(list[12]) - var aNullableEnum: AnEnum? = nil - let aNullableEnumEnumVal: Int? = nilOrValue(list[13]) - if let aNullableEnumRawValue = aNullableEnumEnumVal { - aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)! - } - let aNullableString: String? = nilOrValue(list[14]) - let aNullableObject: Any? = list[15] - var allNullableTypes: AllNullableTypes? = nil - if let allNullableTypesList: [Any?] = nilOrValue(list[16]) { - allNullableTypes = AllNullableTypes.fromList(allNullableTypesList) - } + isNullish(__pigeon_list[2]) + ? nil + : (__pigeon_list[2] is Int64? + ? __pigeon_list[2] as! Int64? : Int64(__pigeon_list[2] as! Int32)) + let aNullableDouble: Double? = nilOrValue(__pigeon_list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[7]) + let nullableNestedList: [[Bool?]?]? = nilOrValue(__pigeon_list[8]) + let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(__pigeon_list[9]) + let nullableMapWithObject: [String?: Any?]? = nilOrValue(__pigeon_list[10]) + let aNullableEnum: AnEnum? = nilOrValue(__pigeon_list[11]) + let aNullableString: String? = nilOrValue(__pigeon_list[12]) + let aNullableObject: Any? = __pigeon_list[13] + let allNullableTypes: AllNullableTypes? = nilOrValue(__pigeon_list[14]) + let list: [Any?]? = nilOrValue(__pigeon_list[15]) + let stringList: [String?]? = nilOrValue(__pigeon_list[16]) + let intList: [Int64?]? = nilOrValue(__pigeon_list[17]) + let doubleList: [Double?]? = nilOrValue(__pigeon_list[18]) + let boolList: [Bool?]? = nilOrValue(__pigeon_list[19]) + let nestedClassList: [AllNullableTypes?]? = nilOrValue(__pigeon_list[20]) + let map: [AnyHashable: Any?]? = nilOrValue(__pigeon_list[21]) return AllNullableTypes( aNullableBool: aNullableBool, @@ -221,15 +285,20 @@ class AllNullableTypes { aNullable4ByteArray: aNullable4ByteArray, aNullable8ByteArray: aNullable8ByteArray, aNullableFloatArray: aNullableFloatArray, - aNullableList: aNullableList, - aNullableMap: aNullableMap, nullableNestedList: nullableNestedList, nullableMapWithAnnotations: nullableMapWithAnnotations, nullableMapWithObject: nullableMapWithObject, aNullableEnum: aNullableEnum, aNullableString: aNullableString, aNullableObject: aNullableObject, - allNullableTypes: allNullableTypes + allNullableTypes: allNullableTypes, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + nestedClassList: nestedClassList, + map: map ) } func toList() -> [Any?] { @@ -242,15 +311,20 @@ class AllNullableTypes { aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.rawValue, + aNullableEnum, aNullableString, aNullableObject, - allNullableTypes?.toList(), + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + nestedClassList, + map, ] } } @@ -269,38 +343,49 @@ struct AllNullableTypesWithoutRecursion { var aNullable4ByteArray: FlutterStandardTypedData? = nil var aNullable8ByteArray: FlutterStandardTypedData? = nil var aNullableFloatArray: FlutterStandardTypedData? = nil - var aNullableList: [Any?]? = nil - var aNullableMap: [AnyHashable: Any?]? = nil var nullableNestedList: [[Bool?]?]? = nil var nullableMapWithAnnotations: [String?: String?]? = nil var nullableMapWithObject: [String?: Any?]? = nil var aNullableEnum: AnEnum? = nil var aNullableString: String? = nil var aNullableObject: Any? = nil + var list: [Any?]? = nil + var stringList: [String?]? = nil + var intList: [Int64?]? = nil + var doubleList: [Double?]? = nil + var boolList: [Bool?]? = nil + var map: [AnyHashable: Any?]? = nil - static func fromList(_ list: [Any?]) -> AllNullableTypesWithoutRecursion? { - let aNullableBool: Bool? = nilOrValue(list[0]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllNullableTypesWithoutRecursion? { + let aNullableBool: Bool? = nilOrValue(__pigeon_list[0]) let aNullableInt: Int64? = - isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) + isNullish(__pigeon_list[1]) + ? nil + : (__pigeon_list[1] is Int64? + ? __pigeon_list[1] as! Int64? : Int64(__pigeon_list[1] as! Int32)) let aNullableInt64: Int64? = - isNullish(list[2]) ? nil : (list[2] is Int64? ? list[2] as! Int64? : Int64(list[2] as! Int32)) - let aNullableDouble: Double? = nilOrValue(list[3]) - let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(list[4]) - let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(list[5]) - let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(list[6]) - let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(list[7]) - let aNullableList: [Any?]? = nilOrValue(list[8]) - let aNullableMap: [AnyHashable: Any?]? = nilOrValue(list[9]) - let nullableNestedList: [[Bool?]?]? = nilOrValue(list[10]) - let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(list[11]) - let nullableMapWithObject: [String?: Any?]? = nilOrValue(list[12]) - var aNullableEnum: AnEnum? = nil - let aNullableEnumEnumVal: Int? = nilOrValue(list[13]) - if let aNullableEnumRawValue = aNullableEnumEnumVal { - aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)! - } - let aNullableString: String? = nilOrValue(list[14]) - let aNullableObject: Any? = list[15] + isNullish(__pigeon_list[2]) + ? nil + : (__pigeon_list[2] is Int64? + ? __pigeon_list[2] as! Int64? : Int64(__pigeon_list[2] as! Int32)) + let aNullableDouble: Double? = nilOrValue(__pigeon_list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[7]) + let nullableNestedList: [[Bool?]?]? = nilOrValue(__pigeon_list[8]) + let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(__pigeon_list[9]) + let nullableMapWithObject: [String?: Any?]? = nilOrValue(__pigeon_list[10]) + let aNullableEnum: AnEnum? = nilOrValue(__pigeon_list[11]) + let aNullableString: String? = nilOrValue(__pigeon_list[12]) + let aNullableObject: Any? = __pigeon_list[13] + let list: [Any?]? = nilOrValue(__pigeon_list[14]) + let stringList: [String?]? = nilOrValue(__pigeon_list[15]) + let intList: [Int64?]? = nilOrValue(__pigeon_list[16]) + let doubleList: [Double?]? = nilOrValue(__pigeon_list[17]) + let boolList: [Bool?]? = nilOrValue(__pigeon_list[18]) + let map: [AnyHashable: Any?]? = nilOrValue(__pigeon_list[19]) return AllNullableTypesWithoutRecursion( aNullableBool: aNullableBool, @@ -311,14 +396,18 @@ struct AllNullableTypesWithoutRecursion { aNullable4ByteArray: aNullable4ByteArray, aNullable8ByteArray: aNullable8ByteArray, aNullableFloatArray: aNullableFloatArray, - aNullableList: aNullableList, - aNullableMap: aNullableMap, nullableNestedList: nullableNestedList, nullableMapWithAnnotations: nullableMapWithAnnotations, nullableMapWithObject: nullableMapWithObject, aNullableEnum: aNullableEnum, aNullableString: aNullableString, - aNullableObject: aNullableObject + aNullableObject: aNullableObject, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map ) } func toList() -> [Any?] { @@ -331,14 +420,18 @@ struct AllNullableTypesWithoutRecursion { aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.rawValue, + aNullableEnum, aNullableString, aNullableObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ] } } @@ -355,17 +448,12 @@ struct AllClassesWrapper { var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil var allTypes: AllTypes? = nil - static func fromList(_ list: [Any?]) -> AllClassesWrapper? { - let allNullableTypes = AllNullableTypes.fromList(list[0] as! [Any?])! - var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil - if let allNullableTypesWithoutRecursionList: [Any?] = nilOrValue(list[1]) { - allNullableTypesWithoutRecursion = AllNullableTypesWithoutRecursion.fromList( - allNullableTypesWithoutRecursionList) - } - var allTypes: AllTypes? = nil - if let allTypesList: [Any?] = nilOrValue(list[2]) { - allTypes = AllTypes.fromList(allTypesList) - } + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllClassesWrapper? { + let allNullableTypes = __pigeon_list[0] as! AllNullableTypes + let allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nilOrValue( + __pigeon_list[1]) + let allTypes: AllTypes? = nilOrValue(__pigeon_list[2]) return AllClassesWrapper( allNullableTypes: allNullableTypes, @@ -375,9 +463,9 @@ struct AllClassesWrapper { } func toList() -> [Any?] { return [ - allNullableTypes.toList(), - allNullableTypesWithoutRecursion?.toList(), - allTypes?.toList(), + allNullableTypes, + allNullableTypesWithoutRecursion, + allTypes, ] } } @@ -388,8 +476,9 @@ struct AllClassesWrapper { struct TestMessage { var testList: [Any?]? = nil - static func fromList(_ list: [Any?]) -> TestMessage? { - let testList: [Any?]? = nilOrValue(list[0]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> TestMessage? { + let testList: [Any?]? = nilOrValue(__pigeon_list[0]) return TestMessage( testList: testList @@ -401,62 +490,70 @@ struct TestMessage { ] } } - -private class HostIntegrationCoreApiCodecReader: FlutterStandardReader { +private class CoreTestsPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { - case 128: - return AllClassesWrapper.fromList(self.readValue() as! [Any?]) case 129: - return AllNullableTypes.fromList(self.readValue() as! [Any?]) + return AllTypes.fromList(self.readValue() as! [Any?]) case 130: - return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) + return AllNullableTypes.fromList(self.readValue() as! [Any?]) case 131: - return AllTypes.fromList(self.readValue() as! [Any?]) + return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) case 132: + return AllClassesWrapper.fromList(self.readValue() as! [Any?]) + case 133: return TestMessage.fromList(self.readValue() as! [Any?]) + case 134: + var enumResult: AnEnum? = nil + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + enumResult = AnEnum(rawValue: enumResultAsInt) + } + return enumResult default: return super.readValue(ofType: type) } } } -private class HostIntegrationCoreApiCodecWriter: FlutterStandardWriter { +private class CoreTestsPigeonCodecWriter: FlutterStandardWriter { override func writeValue(_ value: Any) { - if let value = value as? AllClassesWrapper { - super.writeByte(128) - super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypes { + if let value = value as? AllTypes { super.writeByte(129) super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypesWithoutRecursion { + } else if let value = value as? AllNullableTypes { super.writeByte(130) super.writeValue(value.toList()) - } else if let value = value as? AllTypes { + } else if let value = value as? AllNullableTypesWithoutRecursion { super.writeByte(131) super.writeValue(value.toList()) - } else if let value = value as? TestMessage { + } else if let value = value as? AllClassesWrapper { super.writeByte(132) super.writeValue(value.toList()) + } else if let value = value as? TestMessage { + super.writeByte(133) + super.writeValue(value.toList()) + } else if let value = value as? AnEnum { + super.writeByte(134) + super.writeValue(value.rawValue) } else { super.writeValue(value) } } } -private class HostIntegrationCoreApiCodecReaderWriter: FlutterStandardReaderWriter { +private class CoreTestsPigeonCodecReaderWriter: FlutterStandardReaderWriter { override func reader(with data: Data) -> FlutterStandardReader { - return HostIntegrationCoreApiCodecReader(data: data) + return CoreTestsPigeonCodecReader(data: data) } override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return HostIntegrationCoreApiCodecWriter(data: data) + return CoreTestsPigeonCodecWriter(data: data) } } -class HostIntegrationCoreApiCodec: FlutterStandardMessageCodec { - static let shared = HostIntegrationCoreApiCodec( - readerWriter: HostIntegrationCoreApiCodecReaderWriter()) +class CoreTestsPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = CoreTestsPigeonCodec(readerWriter: CoreTestsPigeonCodecReaderWriter()) } /// The core interface that each host language plugin must implement in @@ -488,7 +585,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed in generic Object. func echo(_ anObject: Any) throws -> Any /// Returns the passed list, to test serialization and deserialization. - func echo(_ aList: [Any?]) throws -> [Any?] + func echo(_ list: [Any?]) throws -> [Any?] /// Returns the passed map, to test serialization and deserialization. func echo(_ aMap: [String?: Any?]) throws -> [String?: Any?] /// Returns the passed map to test nested class serialization and deserialization. @@ -559,7 +656,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed in generic Object asynchronously. func echoAsync(_ anObject: Any, completion: @escaping (Result) -> Void) /// Returns the passed list, to test asynchronous serialization and deserialization. - func echoAsync(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) + func echoAsync(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) /// Returns the passed map, to test asynchronous serialization and deserialization. func echoAsync( _ aMap: [String?: Any?], completion: @escaping (Result<[String?: Any?], Error>) -> Void) @@ -596,7 +693,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed in generic Object asynchronously. func echoAsyncNullable(_ anObject: Any?, completion: @escaping (Result) -> Void) /// Returns the passed list, to test asynchronous serialization and deserialization. - func echoAsyncNullable(_ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) + func echoAsyncNullable(_ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) /// Returns the passed map, to test asynchronous serialization and deserialization. func echoAsyncNullable( _ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) @@ -624,9 +721,9 @@ protocol HostIntegrationCoreApi { func callFlutterEcho(_ aDouble: Double, completion: @escaping (Result) -> Void) func callFlutterEcho(_ aString: String, completion: @escaping (Result) -> Void) func callFlutterEcho( - _ aList: FlutterStandardTypedData, + _ list: FlutterStandardTypedData, completion: @escaping (Result) -> Void) - func callFlutterEcho(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) + func callFlutterEcho(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) func callFlutterEcho( _ aMap: [String?: Any?], completion: @escaping (Result<[String?: Any?], Error>) -> Void) func callFlutterEcho(_ anEnum: AnEnum, completion: @escaping (Result) -> Void) @@ -638,10 +735,10 @@ protocol HostIntegrationCoreApi { func callFlutterEchoNullable( _ aString: String?, completion: @escaping (Result) -> Void) func callFlutterEchoNullable( - _ aList: FlutterStandardTypedData?, + _ list: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) func callFlutterEchoNullable( - _ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) + _ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) func callFlutterEchoNullable( _ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) func callFlutterNullableEcho( @@ -652,8 +749,7 @@ protocol HostIntegrationCoreApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class HostIntegrationCoreApiSetup { - /// The codec used by HostIntegrationCoreApi. - static var codec: FlutterStandardMessageCodec { HostIntegrationCoreApiCodec.shared } + static var codec: FlutterStandardMessageCodec { CoreTestsPigeonCodec.shared } /// Sets up an instance of `HostIntegrationCoreApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: HostIntegrationCoreApi?, @@ -870,9 +966,9 @@ class HostIntegrationCoreApiSetup { if let api = api { echoListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! [Any?] + let listArg = args[0] as! [Any?] do { - let result = try api.echo(aListArg) + let result = try api.echo(listArg) reply(wrapResult(result)) } catch { reply(wrapError(error)) @@ -927,10 +1023,10 @@ class HostIntegrationCoreApiSetup { if let api = api { echoEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg = AnEnum(rawValue: args[0] as! Int)! + let anEnumArg = args[0] as! AnEnum do { let result = try api.echo(anEnumArg) - reply(wrapResult(result.rawValue)) + reply(wrapResult(result)) } catch { reply(wrapError(error)) } @@ -1282,10 +1378,10 @@ class HostIntegrationCoreApiSetup { if let api = api { echoNullableEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg: AnEnum? = isNullish(args[0]) ? nil : AnEnum(rawValue: args[0] as! Int)! + let anEnumArg: AnEnum? = nilOrValue(args[0]) do { let result = try api.echoNullable(anEnumArg) - reply(wrapResult(result?.rawValue)) + reply(wrapResult(result)) } catch { reply(wrapError(error)) } @@ -1487,8 +1583,8 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! [Any?] - api.echoAsync(aListArg) { result in + let listArg = args[0] as! [Any?] + api.echoAsync(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -1529,11 +1625,11 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg = AnEnum(rawValue: args[0] as! Int)! + let anEnumArg = args[0] as! AnEnum api.echoAsync(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -1798,8 +1894,8 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncNullableListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg: [Any?]? = nilOrValue(args[0]) - api.echoAsyncNullable(aListArg) { result in + let listArg: [Any?]? = nilOrValue(args[0]) + api.echoAsyncNullable(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -1840,11 +1936,11 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncNullableEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg: AnEnum? = isNullish(args[0]) ? nil : AnEnum(rawValue: args[0] as! Int)! + let anEnumArg: AnEnum? = nilOrValue(args[0]) api.echoAsyncNullable(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res?.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -2107,8 +2203,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoUint8ListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! FlutterStandardTypedData - api.callFlutterEcho(aListArg) { result in + let listArg = args[0] as! FlutterStandardTypedData + api.callFlutterEcho(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2127,8 +2223,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! [Any?] - api.callFlutterEcho(aListArg) { result in + let listArg = args[0] as! [Any?] + api.callFlutterEcho(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2167,11 +2263,11 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg = AnEnum(rawValue: args[0] as! Int)! + let anEnumArg = args[0] as! AnEnum api.callFlutterEcho(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -2269,8 +2365,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoNullableUint8ListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg: FlutterStandardTypedData? = nilOrValue(args[0]) - api.callFlutterEchoNullable(aListArg) { result in + let listArg: FlutterStandardTypedData? = nilOrValue(args[0]) + api.callFlutterEchoNullable(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2289,8 +2385,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoNullableListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg: [Any?]? = nilOrValue(args[0]) - api.callFlutterEchoNullable(aListArg) { result in + let listArg: [Any?]? = nilOrValue(args[0]) + api.callFlutterEchoNullable(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2329,11 +2425,11 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoNullableEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg: AnEnum? = isNullish(args[0]) ? nil : AnEnum(rawValue: args[0] as! Int)! + let anEnumArg: AnEnum? = nilOrValue(args[0]) api.callFlutterNullableEcho(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res?.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -2364,63 +2460,6 @@ class HostIntegrationCoreApiSetup { } } } -private class FlutterIntegrationCoreApiCodecReader: FlutterStandardReader { - override func readValue(ofType type: UInt8) -> Any? { - switch type { - case 128: - return AllClassesWrapper.fromList(self.readValue() as! [Any?]) - case 129: - return AllNullableTypes.fromList(self.readValue() as! [Any?]) - case 130: - return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) - case 131: - return AllTypes.fromList(self.readValue() as! [Any?]) - case 132: - return TestMessage.fromList(self.readValue() as! [Any?]) - default: - return super.readValue(ofType: type) - } - } -} - -private class FlutterIntegrationCoreApiCodecWriter: FlutterStandardWriter { - override func writeValue(_ value: Any) { - if let value = value as? AllClassesWrapper { - super.writeByte(128) - super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypes { - super.writeByte(129) - super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypesWithoutRecursion { - super.writeByte(130) - super.writeValue(value.toList()) - } else if let value = value as? AllTypes { - super.writeByte(131) - super.writeValue(value.toList()) - } else if let value = value as? TestMessage { - super.writeByte(132) - super.writeValue(value.toList()) - } else { - super.writeValue(value) - } - } -} - -private class FlutterIntegrationCoreApiCodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return FlutterIntegrationCoreApiCodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return FlutterIntegrationCoreApiCodecWriter(data: data) - } -} - -class FlutterIntegrationCoreApiCodec: FlutterStandardMessageCodec { - static let shared = FlutterIntegrationCoreApiCodec( - readerWriter: FlutterIntegrationCoreApiCodecReaderWriter()) -} - /// The core interface that the Dart platform_test code implements for host /// integration tests to call into. /// @@ -2428,86 +2467,85 @@ class FlutterIntegrationCoreApiCodec: FlutterStandardMessageCodec { protocol FlutterIntegrationCoreApiProtocol { /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. - func noop(completion: @escaping (Result) -> Void) + func noop(completion: @escaping (Result) -> Void) /// Responds with an error from an async function returning a value. - func throwError(completion: @escaping (Result) -> Void) + func throwError(completion: @escaping (Result) -> Void) /// Responds with an error from an async void function. - func throwErrorFromVoid(completion: @escaping (Result) -> Void) + func throwErrorFromVoid(completion: @escaping (Result) -> Void) /// Returns the passed object, to test serialization and deserialization. func echo( - _ everythingArg: AllTypes, completion: @escaping (Result) -> Void) + _ everythingArg: AllTypes, completion: @escaping (Result) -> Void) /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypes?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns passed in arguments of multiple types. /// /// Tests multiple-arity FlutterApi handling. func sendMultipleNullableTypes( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypesWithoutRecursion?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns passed in arguments of multiple types. /// /// Tests multiple-arity FlutterApi handling. func sendMultipleNullableTypesWithoutRecursion( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns the passed boolean, to test serialization and deserialization. - func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) + func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) /// Returns the passed int, to test serialization and deserialization. - func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) + func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) /// Returns the passed double, to test serialization and deserialization. - func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) + func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) /// Returns the passed string, to test serialization and deserialization. - func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) + func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) /// Returns the passed byte list, to test serialization and deserialization. func echo( - _ aListArg: FlutterStandardTypedData, - completion: @escaping (Result) -> Void) + _ listArg: FlutterStandardTypedData, + completion: @escaping (Result) -> Void) /// Returns the passed list, to test serialization and deserialization. - func echo(_ aListArg: [Any?], completion: @escaping (Result<[Any?], FlutterError>) -> Void) + func echo(_ listArg: [Any?], completion: @escaping (Result<[Any?], PigeonError>) -> Void) /// Returns the passed map, to test serialization and deserialization. func echo( - _ aMapArg: [String?: Any?], - completion: @escaping (Result<[String?: Any?], FlutterError>) -> Void) + _ aMapArg: [String?: Any?], completion: @escaping (Result<[String?: Any?], PigeonError>) -> Void + ) /// Returns the passed enum to test serialization and deserialization. - func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) + func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) /// Returns the passed boolean, to test serialization and deserialization. - func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) + func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) /// Returns the passed int, to test serialization and deserialization. - func echoNullable( - _ anIntArg: Int64?, completion: @escaping (Result) -> Void) + func echoNullable(_ anIntArg: Int64?, completion: @escaping (Result) -> Void) /// Returns the passed double, to test serialization and deserialization. func echoNullable( - _ aDoubleArg: Double?, completion: @escaping (Result) -> Void) + _ aDoubleArg: Double?, completion: @escaping (Result) -> Void) /// Returns the passed string, to test serialization and deserialization. func echoNullable( - _ aStringArg: String?, completion: @escaping (Result) -> Void) + _ aStringArg: String?, completion: @escaping (Result) -> Void) /// Returns the passed byte list, to test serialization and deserialization. func echoNullable( - _ aListArg: FlutterStandardTypedData?, - completion: @escaping (Result) -> Void) + _ listArg: FlutterStandardTypedData?, + completion: @escaping (Result) -> Void) /// Returns the passed list, to test serialization and deserialization. func echoNullable( - _ aListArg: [Any?]?, completion: @escaping (Result<[Any?]?, FlutterError>) -> Void) + _ listArg: [Any?]?, completion: @escaping (Result<[Any?]?, PigeonError>) -> Void) /// Returns the passed map, to test serialization and deserialization. func echoNullable( _ aMapArg: [String?: Any?]?, - completion: @escaping (Result<[String?: Any?]?, FlutterError>) -> Void) + completion: @escaping (Result<[String?: Any?]?, PigeonError>) -> Void) /// Returns the passed enum to test serialization and deserialization. func echoNullable( - _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void) + _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void) /// A no-op function taking no arguments and returning no value, to sanity /// test basic asynchronous calling. - func noopAsync(completion: @escaping (Result) -> Void) + func noopAsync(completion: @escaping (Result) -> Void) /// Returns the passed in generic Object asynchronously. - func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) + func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) } class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -2516,12 +2554,12 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { self.binaryMessenger = binaryMessenger self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" } - var codec: FlutterStandardMessageCodec { - return FlutterIntegrationCoreApiCodec.shared + var codec: CoreTestsPigeonCodec { + return CoreTestsPigeonCodec.shared } /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. - func noop(completion: @escaping (Result) -> Void) { + func noop(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.noop\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2535,14 +2573,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { completion(.success(Void())) } } } /// Responds with an error from an async function returning a value. - func throwError(completion: @escaping (Result) -> Void) { + func throwError(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.throwError\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2556,7 +2594,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Any? = listResponse[0] completion(.success(result)) @@ -2564,7 +2602,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Responds with an error from an async void function. - func throwErrorFromVoid(completion: @escaping (Result) -> Void) { + func throwErrorFromVoid(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.throwErrorFromVoid\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2578,7 +2616,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { completion(.success(Void())) } @@ -2586,7 +2624,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed object, to test serialization and deserialization. func echo( - _ everythingArg: AllTypes, completion: @escaping (Result) -> Void + _ everythingArg: AllTypes, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllTypes\(messageChannelSuffix)" @@ -2601,11 +2639,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2617,7 +2655,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypes?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypes\(messageChannelSuffix)" @@ -2632,7 +2670,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: AllNullableTypes? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -2645,7 +2683,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { func sendMultipleNullableTypes( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypes\(messageChannelSuffix)" @@ -2661,11 +2699,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2677,7 +2715,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypesWithoutRecursion?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion\(messageChannelSuffix)" @@ -2692,7 +2730,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: AllNullableTypesWithoutRecursion? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -2705,7 +2743,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { func sendMultipleNullableTypesWithoutRecursion( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion\(messageChannelSuffix)" @@ -2721,11 +2759,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2735,7 +2773,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed boolean, to test serialization and deserialization. - func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) { + func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoBool\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2749,11 +2787,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2763,7 +2801,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed int, to test serialization and deserialization. - func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) { + func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoInt\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2777,11 +2815,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2792,7 +2830,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed double, to test serialization and deserialization. - func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) { + func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoDouble\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2806,11 +2844,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2820,7 +2858,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed string, to test serialization and deserialization. - func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) { + func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoString\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2834,11 +2872,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2849,14 +2887,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed byte list, to test serialization and deserialization. func echo( - _ aListArg: FlutterStandardTypedData, - completion: @escaping (Result) -> Void + _ listArg: FlutterStandardTypedData, + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoUint8List\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -2865,11 +2903,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2879,12 +2917,12 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed list, to test serialization and deserialization. - func echo(_ aListArg: [Any?], completion: @escaping (Result<[Any?], FlutterError>) -> Void) { + func echo(_ listArg: [Any?], completion: @escaping (Result<[Any?], PigeonError>) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoList\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -2893,11 +2931,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2908,8 +2946,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed map, to test serialization and deserialization. func echo( - _ aMapArg: [String?: Any?], - completion: @escaping (Result<[String?: Any?], FlutterError>) -> Void + _ aMapArg: [String?: Any?], completion: @escaping (Result<[String?: Any?], PigeonError>) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoMap\(messageChannelSuffix)" @@ -2924,11 +2961,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2938,12 +2975,12 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed enum to test serialization and deserialization. - func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) { + func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoEnum\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([anEnumArg.rawValue] as [Any?]) { response in + channel.sendMessage([anEnumArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -2952,22 +2989,21 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { - let result = AnEnum(rawValue: listResponse[0] as! Int)! + let result = listResponse[0] as! AnEnum completion(.success(result)) } } } /// Returns the passed boolean, to test serialization and deserialization. - func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) - { + func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableBool\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2981,7 +3017,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Bool? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -2989,9 +3025,8 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed int, to test serialization and deserialization. - func echoNullable( - _ anIntArg: Int64?, completion: @escaping (Result) -> Void - ) { + func echoNullable(_ anIntArg: Int64?, completion: @escaping (Result) -> Void) + { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableInt\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -3005,7 +3040,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Int64? = isNullish(listResponse[0]) @@ -3018,7 +3053,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed double, to test serialization and deserialization. func echoNullable( - _ aDoubleArg: Double?, completion: @escaping (Result) -> Void + _ aDoubleArg: Double?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableDouble\(messageChannelSuffix)" @@ -3033,7 +3068,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Double? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3042,7 +3077,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed string, to test serialization and deserialization. func echoNullable( - _ aStringArg: String?, completion: @escaping (Result) -> Void + _ aStringArg: String?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableString\(messageChannelSuffix)" @@ -3057,7 +3092,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: String? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3066,14 +3101,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed byte list, to test serialization and deserialization. func echoNullable( - _ aListArg: FlutterStandardTypedData?, - completion: @escaping (Result) -> Void + _ listArg: FlutterStandardTypedData?, + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableUint8List\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -3082,7 +3117,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: FlutterStandardTypedData? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3091,13 +3126,13 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed list, to test serialization and deserialization. func echoNullable( - _ aListArg: [Any?]?, completion: @escaping (Result<[Any?]?, FlutterError>) -> Void + _ listArg: [Any?]?, completion: @escaping (Result<[Any?]?, PigeonError>) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableList\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -3106,7 +3141,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: [Any?]? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3116,7 +3151,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { /// Returns the passed map, to test serialization and deserialization. func echoNullable( _ aMapArg: [String?: Any?]?, - completion: @escaping (Result<[String?: Any?]?, FlutterError>) -> Void + completion: @escaping (Result<[String?: Any?]?, PigeonError>) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableMap\(messageChannelSuffix)" @@ -3131,7 +3166,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: [String?: Any?]? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3140,13 +3175,13 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed enum to test serialization and deserialization. func echoNullable( - _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void + _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableEnum\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([anEnumArg?.rawValue] as [Any?]) { response in + channel.sendMessage([anEnumArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -3155,17 +3190,16 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { - let result: AnEnum? = - isNullish(listResponse[0]) ? nil : AnEnum(rawValue: listResponse[0] as! Int)! + let result: AnEnum? = nilOrValue(listResponse[0]) completion(.success(result)) } } } /// A no-op function taking no arguments and returning no value, to sanity /// test basic asynchronous calling. - func noopAsync(completion: @escaping (Result) -> Void) { + func noopAsync(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.noopAsync\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -3179,14 +3213,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { completion(.success(Void())) } } } /// Returns the passed in generic Object asynchronously. - func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) + func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAsyncString\(messageChannelSuffix)" @@ -3201,11 +3235,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -3224,7 +3258,7 @@ protocol HostTrivialApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class HostTrivialApiSetup { - /// The codec used by HostTrivialApi. + static var codec: FlutterStandardMessageCodec { CoreTestsPigeonCodec.shared } /// Sets up an instance of `HostTrivialApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: HostTrivialApi?, messageChannelSuffix: String = "" @@ -3232,7 +3266,7 @@ class HostTrivialApiSetup { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let noopChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostTrivialApi.noop\(channelSuffix)", - binaryMessenger: binaryMessenger) + binaryMessenger: binaryMessenger, codec: codec) if let api = api { noopChannel.setMessageHandler { _, reply in do { @@ -3257,7 +3291,7 @@ protocol HostSmallApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class HostSmallApiSetup { - /// The codec used by HostSmallApi. + static var codec: FlutterStandardMessageCodec { CoreTestsPigeonCodec.shared } /// Sets up an instance of `HostSmallApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: HostSmallApi?, messageChannelSuffix: String = "" @@ -3265,7 +3299,7 @@ class HostSmallApiSetup { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let echoChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo\(channelSuffix)", - binaryMessenger: binaryMessenger) + binaryMessenger: binaryMessenger, codec: codec) if let api = api { echoChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -3284,7 +3318,7 @@ class HostSmallApiSetup { } let voidVoidChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.voidVoid\(channelSuffix)", - binaryMessenger: binaryMessenger) + binaryMessenger: binaryMessenger, codec: codec) if let api = api { voidVoidChannel.setMessageHandler { _, reply in api.voidVoid { result in @@ -3301,49 +3335,12 @@ class HostSmallApiSetup { } } } -private class FlutterSmallApiCodecReader: FlutterStandardReader { - override func readValue(ofType type: UInt8) -> Any? { - switch type { - case 128: - return TestMessage.fromList(self.readValue() as! [Any?]) - default: - return super.readValue(ofType: type) - } - } -} - -private class FlutterSmallApiCodecWriter: FlutterStandardWriter { - override func writeValue(_ value: Any) { - if let value = value as? TestMessage { - super.writeByte(128) - super.writeValue(value.toList()) - } else { - super.writeValue(value) - } - } -} - -private class FlutterSmallApiCodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return FlutterSmallApiCodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return FlutterSmallApiCodecWriter(data: data) - } -} - -class FlutterSmallApiCodec: FlutterStandardMessageCodec { - static let shared = FlutterSmallApiCodec(readerWriter: FlutterSmallApiCodecReaderWriter()) -} - /// A simple API called in some unit tests. /// /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol FlutterSmallApiProtocol { - func echo( - _ msgArg: TestMessage, completion: @escaping (Result) -> Void) - func echo(string aStringArg: String, completion: @escaping (Result) -> Void) + func echo(_ msgArg: TestMessage, completion: @escaping (Result) -> Void) + func echo(string aStringArg: String, completion: @escaping (Result) -> Void) } class FlutterSmallApi: FlutterSmallApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -3352,12 +3349,11 @@ class FlutterSmallApi: FlutterSmallApiProtocol { self.binaryMessenger = binaryMessenger self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" } - var codec: FlutterStandardMessageCodec { - return FlutterSmallApiCodec.shared + var codec: CoreTestsPigeonCodec { + return CoreTestsPigeonCodec.shared } - func echo( - _ msgArg: TestMessage, completion: @escaping (Result) -> Void - ) { + func echo(_ msgArg: TestMessage, completion: @escaping (Result) -> Void) + { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi.echoWrappedList\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -3371,11 +3367,11 @@ class FlutterSmallApi: FlutterSmallApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -3384,7 +3380,7 @@ class FlutterSmallApi: FlutterSmallApiProtocol { } } } - func echo(string aStringArg: String, completion: @escaping (Result) -> Void) + func echo(string aStringArg: String, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi.echoString\(messageChannelSuffix)" @@ -3399,11 +3395,11 @@ class FlutterSmallApi: FlutterSmallApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift index dd88abb5a2a..36e6a445b53 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift @@ -5,8 +5,6 @@ import Flutter import UIKit -extension FlutterError: Error {} - /// This plugin handles the native side of the integration tests in /// example/integration_test/. public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { @@ -50,15 +48,15 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func throwError() throws -> Any? { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } func throwErrorFromVoid() throws { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } func throwFlutterError() throws -> Any? { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } func echo(_ anInt: Int64) -> Int64 { @@ -85,8 +83,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { return anObject } - func echo(_ aList: [Any?]) throws -> [Any?] { - return aList + func echo(_ list: [Any?]) throws -> [Any?] { + return list } func echo(_ aMap: [String?: Any?]) throws -> [String?: Any?] { @@ -186,15 +184,15 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func throwAsyncError(completion: @escaping (Result) -> Void) { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) } func throwAsyncErrorFromVoid(completion: @escaping (Result) -> Void) { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) } func throwAsyncFlutterError(completion: @escaping (Result) -> Void) { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) } func echoAsync(_ everything: AllTypes, completion: @escaping (Result) -> Void) { @@ -242,8 +240,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(anObject)) } - func echoAsync(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { - completion(.success(aList)) + func echoAsync(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { + completion(.success(list)) } func echoAsync( @@ -285,8 +283,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(anObject)) } - func echoAsyncNullable(_ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) { - completion(.success(aList)) + func echoAsyncNullable(_ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) { + completion(.success(list)) } func echoAsyncNullable( @@ -457,10 +455,10 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func callFlutterEcho( - _ aList: FlutterStandardTypedData, + _ list: FlutterStandardTypedData, completion: @escaping (Result) -> Void ) { - flutterAPI.echo(aList) { response in + flutterAPI.echo(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -470,8 +468,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } } - func callFlutterEcho(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { - flutterAPI.echo(aList) { response in + func callFlutterEcho(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { + flutterAPI.echo(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -557,10 +555,10 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func callFlutterEchoNullable( - _ aList: FlutterStandardTypedData?, + _ list: FlutterStandardTypedData?, completion: @escaping (Result) -> Void ) { - flutterAPI.echoNullable(aList) { response in + flutterAPI.echoNullable(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -571,9 +569,9 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func callFlutterEchoNullable( - _ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void + _ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void ) { - flutterAPI.echoNullable(aList) { response in + flutterAPI.echoNullable(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -623,7 +621,7 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } else { completion( .failure( - FlutterError( + PigeonError( code: "", message: "Multi-instance responses were not matching: \(resOne), \(resTwo)", details: nil))) diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift index f02daf70167..2b95f040b48 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift @@ -15,11 +15,36 @@ import Foundation #error("Unsupported platform.") #endif +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + private func wrapResult(_ result: Any?) -> [Any?] { return [result] } private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } if let flutterError = error as? FlutterError { return [ flutterError.code, @@ -34,8 +59,8 @@ private func wrapError(_ error: Any) -> [Any?] { ] } -private func createConnectionError(withChannelName channelName: String) -> FlutterError { - return FlutterError( +private func createConnectionError(withChannelName channelName: String) -> PigeonError { + return PigeonError( code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") } @@ -69,26 +94,37 @@ struct AllTypes { var a4ByteArray: FlutterStandardTypedData var a8ByteArray: FlutterStandardTypedData var aFloatArray: FlutterStandardTypedData - var aList: [Any?] - var aMap: [AnyHashable: Any?] var anEnum: AnEnum var aString: String var anObject: Any + var list: [Any?] + var stringList: [String?] + var intList: [Int64?] + var doubleList: [Double?] + var boolList: [Bool?] + var map: [AnyHashable: Any?] - static func fromList(_ list: [Any?]) -> AllTypes? { - let aBool = list[0] as! Bool - let anInt = list[1] is Int64 ? list[1] as! Int64 : Int64(list[1] as! Int32) - let anInt64 = list[2] is Int64 ? list[2] as! Int64 : Int64(list[2] as! Int32) - let aDouble = list[3] as! Double - let aByteArray = list[4] as! FlutterStandardTypedData - let a4ByteArray = list[5] as! FlutterStandardTypedData - let a8ByteArray = list[6] as! FlutterStandardTypedData - let aFloatArray = list[7] as! FlutterStandardTypedData - let aList = list[8] as! [Any?] - let aMap = list[9] as! [AnyHashable: Any?] - let anEnum = AnEnum(rawValue: list[10] as! Int)! - let aString = list[11] as! String - let anObject = list[12]! + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllTypes? { + let aBool = __pigeon_list[0] as! Bool + let anInt = + __pigeon_list[1] is Int64 ? __pigeon_list[1] as! Int64 : Int64(__pigeon_list[1] as! Int32) + let anInt64 = + __pigeon_list[2] is Int64 ? __pigeon_list[2] as! Int64 : Int64(__pigeon_list[2] as! Int32) + let aDouble = __pigeon_list[3] as! Double + let aByteArray = __pigeon_list[4] as! FlutterStandardTypedData + let a4ByteArray = __pigeon_list[5] as! FlutterStandardTypedData + let a8ByteArray = __pigeon_list[6] as! FlutterStandardTypedData + let aFloatArray = __pigeon_list[7] as! FlutterStandardTypedData + let anEnum = __pigeon_list[8] as! AnEnum + let aString = __pigeon_list[9] as! String + let anObject = __pigeon_list[10]! + let list = __pigeon_list[11] as! [Any?] + let stringList = __pigeon_list[12] as! [String?] + let intList = __pigeon_list[13] as! [Int64?] + let doubleList = __pigeon_list[14] as! [Double?] + let boolList = __pigeon_list[15] as! [Bool?] + let map = __pigeon_list[16] as! [AnyHashable: Any?] return AllTypes( aBool: aBool, @@ -99,11 +135,15 @@ struct AllTypes { a4ByteArray: a4ByteArray, a8ByteArray: a8ByteArray, aFloatArray: aFloatArray, - aList: aList, - aMap: aMap, anEnum: anEnum, aString: aString, - anObject: anObject + anObject: anObject, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map ) } func toList() -> [Any?] { @@ -116,11 +156,15 @@ struct AllTypes { a4ByteArray, a8ByteArray, aFloatArray, - aList, - aMap, - anEnum.rawValue, + anEnum, aString, anObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ] } } @@ -138,15 +182,20 @@ class AllNullableTypes { aNullable4ByteArray: FlutterStandardTypedData? = nil, aNullable8ByteArray: FlutterStandardTypedData? = nil, aNullableFloatArray: FlutterStandardTypedData? = nil, - aNullableList: [Any?]? = nil, - aNullableMap: [AnyHashable: Any?]? = nil, nullableNestedList: [[Bool?]?]? = nil, nullableMapWithAnnotations: [String?: String?]? = nil, nullableMapWithObject: [String?: Any?]? = nil, aNullableEnum: AnEnum? = nil, aNullableString: String? = nil, aNullableObject: Any? = nil, - allNullableTypes: AllNullableTypes? = nil + allNullableTypes: AllNullableTypes? = nil, + list: [Any?]? = nil, + stringList: [String?]? = nil, + intList: [Int64?]? = nil, + doubleList: [Double?]? = nil, + boolList: [Bool?]? = nil, + nestedClassList: [AllNullableTypes?]? = nil, + map: [AnyHashable: Any?]? = nil ) { self.aNullableBool = aNullableBool self.aNullableInt = aNullableInt @@ -156,8 +205,6 @@ class AllNullableTypes { self.aNullable4ByteArray = aNullable4ByteArray self.aNullable8ByteArray = aNullable8ByteArray self.aNullableFloatArray = aNullableFloatArray - self.aNullableList = aNullableList - self.aNullableMap = aNullableMap self.nullableNestedList = nullableNestedList self.nullableMapWithAnnotations = nullableMapWithAnnotations self.nullableMapWithObject = nullableMapWithObject @@ -165,6 +212,13 @@ class AllNullableTypes { self.aNullableString = aNullableString self.aNullableObject = aNullableObject self.allNullableTypes = allNullableTypes + self.list = list + self.stringList = stringList + self.intList = intList + self.doubleList = doubleList + self.boolList = boolList + self.nestedClassList = nestedClassList + self.map = map } var aNullableBool: Bool? var aNullableInt: Int64? @@ -174,8 +228,6 @@ class AllNullableTypes { var aNullable4ByteArray: FlutterStandardTypedData? var aNullable8ByteArray: FlutterStandardTypedData? var aNullableFloatArray: FlutterStandardTypedData? - var aNullableList: [Any?]? - var aNullableMap: [AnyHashable: Any?]? var nullableNestedList: [[Bool?]?]? var nullableMapWithAnnotations: [String?: String?]? var nullableMapWithObject: [String?: Any?]? @@ -183,34 +235,46 @@ class AllNullableTypes { var aNullableString: String? var aNullableObject: Any? var allNullableTypes: AllNullableTypes? + var list: [Any?]? + var stringList: [String?]? + var intList: [Int64?]? + var doubleList: [Double?]? + var boolList: [Bool?]? + var nestedClassList: [AllNullableTypes?]? + var map: [AnyHashable: Any?]? - static func fromList(_ list: [Any?]) -> AllNullableTypes? { - let aNullableBool: Bool? = nilOrValue(list[0]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllNullableTypes? { + let aNullableBool: Bool? = nilOrValue(__pigeon_list[0]) let aNullableInt: Int64? = - isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) + isNullish(__pigeon_list[1]) + ? nil + : (__pigeon_list[1] is Int64? + ? __pigeon_list[1] as! Int64? : Int64(__pigeon_list[1] as! Int32)) let aNullableInt64: Int64? = - isNullish(list[2]) ? nil : (list[2] is Int64? ? list[2] as! Int64? : Int64(list[2] as! Int32)) - let aNullableDouble: Double? = nilOrValue(list[3]) - let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(list[4]) - let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(list[5]) - let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(list[6]) - let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(list[7]) - let aNullableList: [Any?]? = nilOrValue(list[8]) - let aNullableMap: [AnyHashable: Any?]? = nilOrValue(list[9]) - let nullableNestedList: [[Bool?]?]? = nilOrValue(list[10]) - let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(list[11]) - let nullableMapWithObject: [String?: Any?]? = nilOrValue(list[12]) - var aNullableEnum: AnEnum? = nil - let aNullableEnumEnumVal: Int? = nilOrValue(list[13]) - if let aNullableEnumRawValue = aNullableEnumEnumVal { - aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)! - } - let aNullableString: String? = nilOrValue(list[14]) - let aNullableObject: Any? = list[15] - var allNullableTypes: AllNullableTypes? = nil - if let allNullableTypesList: [Any?] = nilOrValue(list[16]) { - allNullableTypes = AllNullableTypes.fromList(allNullableTypesList) - } + isNullish(__pigeon_list[2]) + ? nil + : (__pigeon_list[2] is Int64? + ? __pigeon_list[2] as! Int64? : Int64(__pigeon_list[2] as! Int32)) + let aNullableDouble: Double? = nilOrValue(__pigeon_list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[7]) + let nullableNestedList: [[Bool?]?]? = nilOrValue(__pigeon_list[8]) + let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(__pigeon_list[9]) + let nullableMapWithObject: [String?: Any?]? = nilOrValue(__pigeon_list[10]) + let aNullableEnum: AnEnum? = nilOrValue(__pigeon_list[11]) + let aNullableString: String? = nilOrValue(__pigeon_list[12]) + let aNullableObject: Any? = __pigeon_list[13] + let allNullableTypes: AllNullableTypes? = nilOrValue(__pigeon_list[14]) + let list: [Any?]? = nilOrValue(__pigeon_list[15]) + let stringList: [String?]? = nilOrValue(__pigeon_list[16]) + let intList: [Int64?]? = nilOrValue(__pigeon_list[17]) + let doubleList: [Double?]? = nilOrValue(__pigeon_list[18]) + let boolList: [Bool?]? = nilOrValue(__pigeon_list[19]) + let nestedClassList: [AllNullableTypes?]? = nilOrValue(__pigeon_list[20]) + let map: [AnyHashable: Any?]? = nilOrValue(__pigeon_list[21]) return AllNullableTypes( aNullableBool: aNullableBool, @@ -221,15 +285,20 @@ class AllNullableTypes { aNullable4ByteArray: aNullable4ByteArray, aNullable8ByteArray: aNullable8ByteArray, aNullableFloatArray: aNullableFloatArray, - aNullableList: aNullableList, - aNullableMap: aNullableMap, nullableNestedList: nullableNestedList, nullableMapWithAnnotations: nullableMapWithAnnotations, nullableMapWithObject: nullableMapWithObject, aNullableEnum: aNullableEnum, aNullableString: aNullableString, aNullableObject: aNullableObject, - allNullableTypes: allNullableTypes + allNullableTypes: allNullableTypes, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + nestedClassList: nestedClassList, + map: map ) } func toList() -> [Any?] { @@ -242,15 +311,20 @@ class AllNullableTypes { aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.rawValue, + aNullableEnum, aNullableString, aNullableObject, - allNullableTypes?.toList(), + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + nestedClassList, + map, ] } } @@ -269,38 +343,49 @@ struct AllNullableTypesWithoutRecursion { var aNullable4ByteArray: FlutterStandardTypedData? = nil var aNullable8ByteArray: FlutterStandardTypedData? = nil var aNullableFloatArray: FlutterStandardTypedData? = nil - var aNullableList: [Any?]? = nil - var aNullableMap: [AnyHashable: Any?]? = nil var nullableNestedList: [[Bool?]?]? = nil var nullableMapWithAnnotations: [String?: String?]? = nil var nullableMapWithObject: [String?: Any?]? = nil var aNullableEnum: AnEnum? = nil var aNullableString: String? = nil var aNullableObject: Any? = nil + var list: [Any?]? = nil + var stringList: [String?]? = nil + var intList: [Int64?]? = nil + var doubleList: [Double?]? = nil + var boolList: [Bool?]? = nil + var map: [AnyHashable: Any?]? = nil - static func fromList(_ list: [Any?]) -> AllNullableTypesWithoutRecursion? { - let aNullableBool: Bool? = nilOrValue(list[0]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllNullableTypesWithoutRecursion? { + let aNullableBool: Bool? = nilOrValue(__pigeon_list[0]) let aNullableInt: Int64? = - isNullish(list[1]) ? nil : (list[1] is Int64? ? list[1] as! Int64? : Int64(list[1] as! Int32)) + isNullish(__pigeon_list[1]) + ? nil + : (__pigeon_list[1] is Int64? + ? __pigeon_list[1] as! Int64? : Int64(__pigeon_list[1] as! Int32)) let aNullableInt64: Int64? = - isNullish(list[2]) ? nil : (list[2] is Int64? ? list[2] as! Int64? : Int64(list[2] as! Int32)) - let aNullableDouble: Double? = nilOrValue(list[3]) - let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(list[4]) - let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(list[5]) - let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(list[6]) - let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(list[7]) - let aNullableList: [Any?]? = nilOrValue(list[8]) - let aNullableMap: [AnyHashable: Any?]? = nilOrValue(list[9]) - let nullableNestedList: [[Bool?]?]? = nilOrValue(list[10]) - let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(list[11]) - let nullableMapWithObject: [String?: Any?]? = nilOrValue(list[12]) - var aNullableEnum: AnEnum? = nil - let aNullableEnumEnumVal: Int? = nilOrValue(list[13]) - if let aNullableEnumRawValue = aNullableEnumEnumVal { - aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)! - } - let aNullableString: String? = nilOrValue(list[14]) - let aNullableObject: Any? = list[15] + isNullish(__pigeon_list[2]) + ? nil + : (__pigeon_list[2] is Int64? + ? __pigeon_list[2] as! Int64? : Int64(__pigeon_list[2] as! Int32)) + let aNullableDouble: Double? = nilOrValue(__pigeon_list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(__pigeon_list[7]) + let nullableNestedList: [[Bool?]?]? = nilOrValue(__pigeon_list[8]) + let nullableMapWithAnnotations: [String?: String?]? = nilOrValue(__pigeon_list[9]) + let nullableMapWithObject: [String?: Any?]? = nilOrValue(__pigeon_list[10]) + let aNullableEnum: AnEnum? = nilOrValue(__pigeon_list[11]) + let aNullableString: String? = nilOrValue(__pigeon_list[12]) + let aNullableObject: Any? = __pigeon_list[13] + let list: [Any?]? = nilOrValue(__pigeon_list[14]) + let stringList: [String?]? = nilOrValue(__pigeon_list[15]) + let intList: [Int64?]? = nilOrValue(__pigeon_list[16]) + let doubleList: [Double?]? = nilOrValue(__pigeon_list[17]) + let boolList: [Bool?]? = nilOrValue(__pigeon_list[18]) + let map: [AnyHashable: Any?]? = nilOrValue(__pigeon_list[19]) return AllNullableTypesWithoutRecursion( aNullableBool: aNullableBool, @@ -311,14 +396,18 @@ struct AllNullableTypesWithoutRecursion { aNullable4ByteArray: aNullable4ByteArray, aNullable8ByteArray: aNullable8ByteArray, aNullableFloatArray: aNullableFloatArray, - aNullableList: aNullableList, - aNullableMap: aNullableMap, nullableNestedList: nullableNestedList, nullableMapWithAnnotations: nullableMapWithAnnotations, nullableMapWithObject: nullableMapWithObject, aNullableEnum: aNullableEnum, aNullableString: aNullableString, - aNullableObject: aNullableObject + aNullableObject: aNullableObject, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + map: map ) } func toList() -> [Any?] { @@ -331,14 +420,18 @@ struct AllNullableTypesWithoutRecursion { aNullable4ByteArray, aNullable8ByteArray, aNullableFloatArray, - aNullableList, - aNullableMap, nullableNestedList, nullableMapWithAnnotations, nullableMapWithObject, - aNullableEnum?.rawValue, + aNullableEnum, aNullableString, aNullableObject, + list, + stringList, + intList, + doubleList, + boolList, + map, ] } } @@ -355,17 +448,12 @@ struct AllClassesWrapper { var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil var allTypes: AllTypes? = nil - static func fromList(_ list: [Any?]) -> AllClassesWrapper? { - let allNullableTypes = AllNullableTypes.fromList(list[0] as! [Any?])! - var allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nil - if let allNullableTypesWithoutRecursionList: [Any?] = nilOrValue(list[1]) { - allNullableTypesWithoutRecursion = AllNullableTypesWithoutRecursion.fromList( - allNullableTypesWithoutRecursionList) - } - var allTypes: AllTypes? = nil - if let allTypesList: [Any?] = nilOrValue(list[2]) { - allTypes = AllTypes.fromList(allTypesList) - } + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> AllClassesWrapper? { + let allNullableTypes = __pigeon_list[0] as! AllNullableTypes + let allNullableTypesWithoutRecursion: AllNullableTypesWithoutRecursion? = nilOrValue( + __pigeon_list[1]) + let allTypes: AllTypes? = nilOrValue(__pigeon_list[2]) return AllClassesWrapper( allNullableTypes: allNullableTypes, @@ -375,9 +463,9 @@ struct AllClassesWrapper { } func toList() -> [Any?] { return [ - allNullableTypes.toList(), - allNullableTypesWithoutRecursion?.toList(), - allTypes?.toList(), + allNullableTypes, + allNullableTypesWithoutRecursion, + allTypes, ] } } @@ -388,8 +476,9 @@ struct AllClassesWrapper { struct TestMessage { var testList: [Any?]? = nil - static func fromList(_ list: [Any?]) -> TestMessage? { - let testList: [Any?]? = nilOrValue(list[0]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> TestMessage? { + let testList: [Any?]? = nilOrValue(__pigeon_list[0]) return TestMessage( testList: testList @@ -401,62 +490,70 @@ struct TestMessage { ] } } - -private class HostIntegrationCoreApiCodecReader: FlutterStandardReader { +private class CoreTestsPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { - case 128: - return AllClassesWrapper.fromList(self.readValue() as! [Any?]) case 129: - return AllNullableTypes.fromList(self.readValue() as! [Any?]) + return AllTypes.fromList(self.readValue() as! [Any?]) case 130: - return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) + return AllNullableTypes.fromList(self.readValue() as! [Any?]) case 131: - return AllTypes.fromList(self.readValue() as! [Any?]) + return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) case 132: + return AllClassesWrapper.fromList(self.readValue() as! [Any?]) + case 133: return TestMessage.fromList(self.readValue() as! [Any?]) + case 134: + var enumResult: AnEnum? = nil + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + enumResult = AnEnum(rawValue: enumResultAsInt) + } + return enumResult default: return super.readValue(ofType: type) } } } -private class HostIntegrationCoreApiCodecWriter: FlutterStandardWriter { +private class CoreTestsPigeonCodecWriter: FlutterStandardWriter { override func writeValue(_ value: Any) { - if let value = value as? AllClassesWrapper { - super.writeByte(128) - super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypes { + if let value = value as? AllTypes { super.writeByte(129) super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypesWithoutRecursion { + } else if let value = value as? AllNullableTypes { super.writeByte(130) super.writeValue(value.toList()) - } else if let value = value as? AllTypes { + } else if let value = value as? AllNullableTypesWithoutRecursion { super.writeByte(131) super.writeValue(value.toList()) - } else if let value = value as? TestMessage { + } else if let value = value as? AllClassesWrapper { super.writeByte(132) super.writeValue(value.toList()) + } else if let value = value as? TestMessage { + super.writeByte(133) + super.writeValue(value.toList()) + } else if let value = value as? AnEnum { + super.writeByte(134) + super.writeValue(value.rawValue) } else { super.writeValue(value) } } } -private class HostIntegrationCoreApiCodecReaderWriter: FlutterStandardReaderWriter { +private class CoreTestsPigeonCodecReaderWriter: FlutterStandardReaderWriter { override func reader(with data: Data) -> FlutterStandardReader { - return HostIntegrationCoreApiCodecReader(data: data) + return CoreTestsPigeonCodecReader(data: data) } override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return HostIntegrationCoreApiCodecWriter(data: data) + return CoreTestsPigeonCodecWriter(data: data) } } -class HostIntegrationCoreApiCodec: FlutterStandardMessageCodec { - static let shared = HostIntegrationCoreApiCodec( - readerWriter: HostIntegrationCoreApiCodecReaderWriter()) +class CoreTestsPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = CoreTestsPigeonCodec(readerWriter: CoreTestsPigeonCodecReaderWriter()) } /// The core interface that each host language plugin must implement in @@ -488,7 +585,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed in generic Object. func echo(_ anObject: Any) throws -> Any /// Returns the passed list, to test serialization and deserialization. - func echo(_ aList: [Any?]) throws -> [Any?] + func echo(_ list: [Any?]) throws -> [Any?] /// Returns the passed map, to test serialization and deserialization. func echo(_ aMap: [String?: Any?]) throws -> [String?: Any?] /// Returns the passed map to test nested class serialization and deserialization. @@ -559,7 +656,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed in generic Object asynchronously. func echoAsync(_ anObject: Any, completion: @escaping (Result) -> Void) /// Returns the passed list, to test asynchronous serialization and deserialization. - func echoAsync(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) + func echoAsync(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) /// Returns the passed map, to test asynchronous serialization and deserialization. func echoAsync( _ aMap: [String?: Any?], completion: @escaping (Result<[String?: Any?], Error>) -> Void) @@ -596,7 +693,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed in generic Object asynchronously. func echoAsyncNullable(_ anObject: Any?, completion: @escaping (Result) -> Void) /// Returns the passed list, to test asynchronous serialization and deserialization. - func echoAsyncNullable(_ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) + func echoAsyncNullable(_ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) /// Returns the passed map, to test asynchronous serialization and deserialization. func echoAsyncNullable( _ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) @@ -624,9 +721,9 @@ protocol HostIntegrationCoreApi { func callFlutterEcho(_ aDouble: Double, completion: @escaping (Result) -> Void) func callFlutterEcho(_ aString: String, completion: @escaping (Result) -> Void) func callFlutterEcho( - _ aList: FlutterStandardTypedData, + _ list: FlutterStandardTypedData, completion: @escaping (Result) -> Void) - func callFlutterEcho(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) + func callFlutterEcho(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) func callFlutterEcho( _ aMap: [String?: Any?], completion: @escaping (Result<[String?: Any?], Error>) -> Void) func callFlutterEcho(_ anEnum: AnEnum, completion: @escaping (Result) -> Void) @@ -638,10 +735,10 @@ protocol HostIntegrationCoreApi { func callFlutterEchoNullable( _ aString: String?, completion: @escaping (Result) -> Void) func callFlutterEchoNullable( - _ aList: FlutterStandardTypedData?, + _ list: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) func callFlutterEchoNullable( - _ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) + _ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) func callFlutterEchoNullable( _ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) func callFlutterNullableEcho( @@ -652,8 +749,7 @@ protocol HostIntegrationCoreApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class HostIntegrationCoreApiSetup { - /// The codec used by HostIntegrationCoreApi. - static var codec: FlutterStandardMessageCodec { HostIntegrationCoreApiCodec.shared } + static var codec: FlutterStandardMessageCodec { CoreTestsPigeonCodec.shared } /// Sets up an instance of `HostIntegrationCoreApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: HostIntegrationCoreApi?, @@ -870,9 +966,9 @@ class HostIntegrationCoreApiSetup { if let api = api { echoListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! [Any?] + let listArg = args[0] as! [Any?] do { - let result = try api.echo(aListArg) + let result = try api.echo(listArg) reply(wrapResult(result)) } catch { reply(wrapError(error)) @@ -927,10 +1023,10 @@ class HostIntegrationCoreApiSetup { if let api = api { echoEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg = AnEnum(rawValue: args[0] as! Int)! + let anEnumArg = args[0] as! AnEnum do { let result = try api.echo(anEnumArg) - reply(wrapResult(result.rawValue)) + reply(wrapResult(result)) } catch { reply(wrapError(error)) } @@ -1282,10 +1378,10 @@ class HostIntegrationCoreApiSetup { if let api = api { echoNullableEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg: AnEnum? = isNullish(args[0]) ? nil : AnEnum(rawValue: args[0] as! Int)! + let anEnumArg: AnEnum? = nilOrValue(args[0]) do { let result = try api.echoNullable(anEnumArg) - reply(wrapResult(result?.rawValue)) + reply(wrapResult(result)) } catch { reply(wrapError(error)) } @@ -1487,8 +1583,8 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! [Any?] - api.echoAsync(aListArg) { result in + let listArg = args[0] as! [Any?] + api.echoAsync(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -1529,11 +1625,11 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg = AnEnum(rawValue: args[0] as! Int)! + let anEnumArg = args[0] as! AnEnum api.echoAsync(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -1798,8 +1894,8 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncNullableListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg: [Any?]? = nilOrValue(args[0]) - api.echoAsyncNullable(aListArg) { result in + let listArg: [Any?]? = nilOrValue(args[0]) + api.echoAsyncNullable(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -1840,11 +1936,11 @@ class HostIntegrationCoreApiSetup { if let api = api { echoAsyncNullableEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg: AnEnum? = isNullish(args[0]) ? nil : AnEnum(rawValue: args[0] as! Int)! + let anEnumArg: AnEnum? = nilOrValue(args[0]) api.echoAsyncNullable(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res?.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -2107,8 +2203,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoUint8ListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! FlutterStandardTypedData - api.callFlutterEcho(aListArg) { result in + let listArg = args[0] as! FlutterStandardTypedData + api.callFlutterEcho(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2127,8 +2223,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg = args[0] as! [Any?] - api.callFlutterEcho(aListArg) { result in + let listArg = args[0] as! [Any?] + api.callFlutterEcho(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2167,11 +2263,11 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg = AnEnum(rawValue: args[0] as! Int)! + let anEnumArg = args[0] as! AnEnum api.callFlutterEcho(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -2269,8 +2365,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoNullableUint8ListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg: FlutterStandardTypedData? = nilOrValue(args[0]) - api.callFlutterEchoNullable(aListArg) { result in + let listArg: FlutterStandardTypedData? = nilOrValue(args[0]) + api.callFlutterEchoNullable(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2289,8 +2385,8 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoNullableListChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let aListArg: [Any?]? = nilOrValue(args[0]) - api.callFlutterEchoNullable(aListArg) { result in + let listArg: [Any?]? = nilOrValue(args[0]) + api.callFlutterEchoNullable(listArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -2329,11 +2425,11 @@ class HostIntegrationCoreApiSetup { if let api = api { callFlutterEchoNullableEnumChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let anEnumArg: AnEnum? = isNullish(args[0]) ? nil : AnEnum(rawValue: args[0] as! Int)! + let anEnumArg: AnEnum? = nilOrValue(args[0]) api.callFlutterNullableEcho(anEnumArg) { result in switch result { case .success(let res): - reply(wrapResult(res?.rawValue)) + reply(wrapResult(res)) case .failure(let error): reply(wrapError(error)) } @@ -2364,63 +2460,6 @@ class HostIntegrationCoreApiSetup { } } } -private class FlutterIntegrationCoreApiCodecReader: FlutterStandardReader { - override func readValue(ofType type: UInt8) -> Any? { - switch type { - case 128: - return AllClassesWrapper.fromList(self.readValue() as! [Any?]) - case 129: - return AllNullableTypes.fromList(self.readValue() as! [Any?]) - case 130: - return AllNullableTypesWithoutRecursion.fromList(self.readValue() as! [Any?]) - case 131: - return AllTypes.fromList(self.readValue() as! [Any?]) - case 132: - return TestMessage.fromList(self.readValue() as! [Any?]) - default: - return super.readValue(ofType: type) - } - } -} - -private class FlutterIntegrationCoreApiCodecWriter: FlutterStandardWriter { - override func writeValue(_ value: Any) { - if let value = value as? AllClassesWrapper { - super.writeByte(128) - super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypes { - super.writeByte(129) - super.writeValue(value.toList()) - } else if let value = value as? AllNullableTypesWithoutRecursion { - super.writeByte(130) - super.writeValue(value.toList()) - } else if let value = value as? AllTypes { - super.writeByte(131) - super.writeValue(value.toList()) - } else if let value = value as? TestMessage { - super.writeByte(132) - super.writeValue(value.toList()) - } else { - super.writeValue(value) - } - } -} - -private class FlutterIntegrationCoreApiCodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return FlutterIntegrationCoreApiCodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return FlutterIntegrationCoreApiCodecWriter(data: data) - } -} - -class FlutterIntegrationCoreApiCodec: FlutterStandardMessageCodec { - static let shared = FlutterIntegrationCoreApiCodec( - readerWriter: FlutterIntegrationCoreApiCodecReaderWriter()) -} - /// The core interface that the Dart platform_test code implements for host /// integration tests to call into. /// @@ -2428,86 +2467,85 @@ class FlutterIntegrationCoreApiCodec: FlutterStandardMessageCodec { protocol FlutterIntegrationCoreApiProtocol { /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. - func noop(completion: @escaping (Result) -> Void) + func noop(completion: @escaping (Result) -> Void) /// Responds with an error from an async function returning a value. - func throwError(completion: @escaping (Result) -> Void) + func throwError(completion: @escaping (Result) -> Void) /// Responds with an error from an async void function. - func throwErrorFromVoid(completion: @escaping (Result) -> Void) + func throwErrorFromVoid(completion: @escaping (Result) -> Void) /// Returns the passed object, to test serialization and deserialization. func echo( - _ everythingArg: AllTypes, completion: @escaping (Result) -> Void) + _ everythingArg: AllTypes, completion: @escaping (Result) -> Void) /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypes?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns passed in arguments of multiple types. /// /// Tests multiple-arity FlutterApi handling. func sendMultipleNullableTypes( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypesWithoutRecursion?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns passed in arguments of multiple types. /// /// Tests multiple-arity FlutterApi handling. func sendMultipleNullableTypesWithoutRecursion( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) /// Returns the passed boolean, to test serialization and deserialization. - func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) + func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) /// Returns the passed int, to test serialization and deserialization. - func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) + func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) /// Returns the passed double, to test serialization and deserialization. - func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) + func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) /// Returns the passed string, to test serialization and deserialization. - func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) + func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) /// Returns the passed byte list, to test serialization and deserialization. func echo( - _ aListArg: FlutterStandardTypedData, - completion: @escaping (Result) -> Void) + _ listArg: FlutterStandardTypedData, + completion: @escaping (Result) -> Void) /// Returns the passed list, to test serialization and deserialization. - func echo(_ aListArg: [Any?], completion: @escaping (Result<[Any?], FlutterError>) -> Void) + func echo(_ listArg: [Any?], completion: @escaping (Result<[Any?], PigeonError>) -> Void) /// Returns the passed map, to test serialization and deserialization. func echo( - _ aMapArg: [String?: Any?], - completion: @escaping (Result<[String?: Any?], FlutterError>) -> Void) + _ aMapArg: [String?: Any?], completion: @escaping (Result<[String?: Any?], PigeonError>) -> Void + ) /// Returns the passed enum to test serialization and deserialization. - func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) + func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) /// Returns the passed boolean, to test serialization and deserialization. - func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) + func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) /// Returns the passed int, to test serialization and deserialization. - func echoNullable( - _ anIntArg: Int64?, completion: @escaping (Result) -> Void) + func echoNullable(_ anIntArg: Int64?, completion: @escaping (Result) -> Void) /// Returns the passed double, to test serialization and deserialization. func echoNullable( - _ aDoubleArg: Double?, completion: @escaping (Result) -> Void) + _ aDoubleArg: Double?, completion: @escaping (Result) -> Void) /// Returns the passed string, to test serialization and deserialization. func echoNullable( - _ aStringArg: String?, completion: @escaping (Result) -> Void) + _ aStringArg: String?, completion: @escaping (Result) -> Void) /// Returns the passed byte list, to test serialization and deserialization. func echoNullable( - _ aListArg: FlutterStandardTypedData?, - completion: @escaping (Result) -> Void) + _ listArg: FlutterStandardTypedData?, + completion: @escaping (Result) -> Void) /// Returns the passed list, to test serialization and deserialization. func echoNullable( - _ aListArg: [Any?]?, completion: @escaping (Result<[Any?]?, FlutterError>) -> Void) + _ listArg: [Any?]?, completion: @escaping (Result<[Any?]?, PigeonError>) -> Void) /// Returns the passed map, to test serialization and deserialization. func echoNullable( _ aMapArg: [String?: Any?]?, - completion: @escaping (Result<[String?: Any?]?, FlutterError>) -> Void) + completion: @escaping (Result<[String?: Any?]?, PigeonError>) -> Void) /// Returns the passed enum to test serialization and deserialization. func echoNullable( - _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void) + _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void) /// A no-op function taking no arguments and returning no value, to sanity /// test basic asynchronous calling. - func noopAsync(completion: @escaping (Result) -> Void) + func noopAsync(completion: @escaping (Result) -> Void) /// Returns the passed in generic Object asynchronously. - func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) + func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) } class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -2516,12 +2554,12 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { self.binaryMessenger = binaryMessenger self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" } - var codec: FlutterStandardMessageCodec { - return FlutterIntegrationCoreApiCodec.shared + var codec: CoreTestsPigeonCodec { + return CoreTestsPigeonCodec.shared } /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. - func noop(completion: @escaping (Result) -> Void) { + func noop(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.noop\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2535,14 +2573,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { completion(.success(Void())) } } } /// Responds with an error from an async function returning a value. - func throwError(completion: @escaping (Result) -> Void) { + func throwError(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.throwError\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2556,7 +2594,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Any? = listResponse[0] completion(.success(result)) @@ -2564,7 +2602,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Responds with an error from an async void function. - func throwErrorFromVoid(completion: @escaping (Result) -> Void) { + func throwErrorFromVoid(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.throwErrorFromVoid\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2578,7 +2616,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { completion(.success(Void())) } @@ -2586,7 +2624,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed object, to test serialization and deserialization. func echo( - _ everythingArg: AllTypes, completion: @escaping (Result) -> Void + _ everythingArg: AllTypes, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllTypes\(messageChannelSuffix)" @@ -2601,11 +2639,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2617,7 +2655,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypes?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypes\(messageChannelSuffix)" @@ -2632,7 +2670,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: AllNullableTypes? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -2645,7 +2683,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { func sendMultipleNullableTypes( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypes\(messageChannelSuffix)" @@ -2661,11 +2699,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2677,7 +2715,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { /// Returns the passed object, to test serialization and deserialization. func echoNullable( _ everythingArg: AllNullableTypesWithoutRecursion?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAllNullableTypesWithoutRecursion\(messageChannelSuffix)" @@ -2692,7 +2730,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: AllNullableTypesWithoutRecursion? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -2705,7 +2743,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { func sendMultipleNullableTypesWithoutRecursion( aBool aNullableBoolArg: Bool?, anInt aNullableIntArg: Int64?, aString aNullableStringArg: String?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.sendMultipleNullableTypesWithoutRecursion\(messageChannelSuffix)" @@ -2721,11 +2759,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2735,7 +2773,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed boolean, to test serialization and deserialization. - func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) { + func echo(_ aBoolArg: Bool, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoBool\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2749,11 +2787,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2763,7 +2801,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed int, to test serialization and deserialization. - func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) { + func echo(_ anIntArg: Int64, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoInt\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2777,11 +2815,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2792,7 +2830,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed double, to test serialization and deserialization. - func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) { + func echo(_ aDoubleArg: Double, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoDouble\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2806,11 +2844,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2820,7 +2858,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed string, to test serialization and deserialization. - func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) { + func echo(_ aStringArg: String, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoString\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2834,11 +2872,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2849,14 +2887,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed byte list, to test serialization and deserialization. func echo( - _ aListArg: FlutterStandardTypedData, - completion: @escaping (Result) -> Void + _ listArg: FlutterStandardTypedData, + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoUint8List\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -2865,11 +2903,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2879,12 +2917,12 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed list, to test serialization and deserialization. - func echo(_ aListArg: [Any?], completion: @escaping (Result<[Any?], FlutterError>) -> Void) { + func echo(_ listArg: [Any?], completion: @escaping (Result<[Any?], PigeonError>) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoList\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -2893,11 +2931,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2908,8 +2946,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed map, to test serialization and deserialization. func echo( - _ aMapArg: [String?: Any?], - completion: @escaping (Result<[String?: Any?], FlutterError>) -> Void + _ aMapArg: [String?: Any?], completion: @escaping (Result<[String?: Any?], PigeonError>) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoMap\(messageChannelSuffix)" @@ -2924,11 +2961,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -2938,12 +2975,12 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed enum to test serialization and deserialization. - func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) { + func echo(_ anEnumArg: AnEnum, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoEnum\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([anEnumArg.rawValue] as [Any?]) { response in + channel.sendMessage([anEnumArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -2952,22 +2989,21 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { - let result = AnEnum(rawValue: listResponse[0] as! Int)! + let result = listResponse[0] as! AnEnum completion(.success(result)) } } } /// Returns the passed boolean, to test serialization and deserialization. - func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) - { + func echoNullable(_ aBoolArg: Bool?, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableBool\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -2981,7 +3017,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Bool? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -2989,9 +3025,8 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } } /// Returns the passed int, to test serialization and deserialization. - func echoNullable( - _ anIntArg: Int64?, completion: @escaping (Result) -> Void - ) { + func echoNullable(_ anIntArg: Int64?, completion: @escaping (Result) -> Void) + { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableInt\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -3005,7 +3040,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Int64? = isNullish(listResponse[0]) @@ -3018,7 +3053,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed double, to test serialization and deserialization. func echoNullable( - _ aDoubleArg: Double?, completion: @escaping (Result) -> Void + _ aDoubleArg: Double?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableDouble\(messageChannelSuffix)" @@ -3033,7 +3068,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: Double? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3042,7 +3077,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed string, to test serialization and deserialization. func echoNullable( - _ aStringArg: String?, completion: @escaping (Result) -> Void + _ aStringArg: String?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableString\(messageChannelSuffix)" @@ -3057,7 +3092,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: String? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3066,14 +3101,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed byte list, to test serialization and deserialization. func echoNullable( - _ aListArg: FlutterStandardTypedData?, - completion: @escaping (Result) -> Void + _ listArg: FlutterStandardTypedData?, + completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableUint8List\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -3082,7 +3117,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: FlutterStandardTypedData? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3091,13 +3126,13 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed list, to test serialization and deserialization. func echoNullable( - _ aListArg: [Any?]?, completion: @escaping (Result<[Any?]?, FlutterError>) -> Void + _ listArg: [Any?]?, completion: @escaping (Result<[Any?]?, PigeonError>) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableList\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([aListArg] as [Any?]) { response in + channel.sendMessage([listArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -3106,7 +3141,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: [Any?]? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3116,7 +3151,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { /// Returns the passed map, to test serialization and deserialization. func echoNullable( _ aMapArg: [String?: Any?]?, - completion: @escaping (Result<[String?: Any?]?, FlutterError>) -> Void + completion: @escaping (Result<[String?: Any?]?, PigeonError>) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableMap\(messageChannelSuffix)" @@ -3131,7 +3166,7 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { let result: [String?: Any?]? = nilOrValue(listResponse[0]) completion(.success(result)) @@ -3140,13 +3175,13 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { } /// Returns the passed enum to test serialization and deserialization. func echoNullable( - _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void + _ anEnumArg: AnEnum?, completion: @escaping (Result) -> Void ) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoNullableEnum\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([anEnumArg?.rawValue] as [Any?]) { response in + channel.sendMessage([anEnumArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return @@ -3155,17 +3190,16 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { - let result: AnEnum? = - isNullish(listResponse[0]) ? nil : AnEnum(rawValue: listResponse[0] as! Int)! + let result: AnEnum? = nilOrValue(listResponse[0]) completion(.success(result)) } } } /// A no-op function taking no arguments and returning no value, to sanity /// test basic asynchronous calling. - func noopAsync(completion: @escaping (Result) -> Void) { + func noopAsync(completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.noopAsync\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -3179,14 +3213,14 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else { completion(.success(Void())) } } } /// Returns the passed in generic Object asynchronously. - func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) + func echoAsync(_ aStringArg: String, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterIntegrationCoreApi.echoAsyncString\(messageChannelSuffix)" @@ -3201,11 +3235,11 @@ class FlutterIntegrationCoreApi: FlutterIntegrationCoreApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -3224,7 +3258,7 @@ protocol HostTrivialApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class HostTrivialApiSetup { - /// The codec used by HostTrivialApi. + static var codec: FlutterStandardMessageCodec { CoreTestsPigeonCodec.shared } /// Sets up an instance of `HostTrivialApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: HostTrivialApi?, messageChannelSuffix: String = "" @@ -3232,7 +3266,7 @@ class HostTrivialApiSetup { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let noopChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostTrivialApi.noop\(channelSuffix)", - binaryMessenger: binaryMessenger) + binaryMessenger: binaryMessenger, codec: codec) if let api = api { noopChannel.setMessageHandler { _, reply in do { @@ -3257,7 +3291,7 @@ protocol HostSmallApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class HostSmallApiSetup { - /// The codec used by HostSmallApi. + static var codec: FlutterStandardMessageCodec { CoreTestsPigeonCodec.shared } /// Sets up an instance of `HostSmallApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: HostSmallApi?, messageChannelSuffix: String = "" @@ -3265,7 +3299,7 @@ class HostSmallApiSetup { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let echoChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.echo\(channelSuffix)", - binaryMessenger: binaryMessenger) + binaryMessenger: binaryMessenger, codec: codec) if let api = api { echoChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -3284,7 +3318,7 @@ class HostSmallApiSetup { } let voidVoidChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.pigeon_integration_tests.HostSmallApi.voidVoid\(channelSuffix)", - binaryMessenger: binaryMessenger) + binaryMessenger: binaryMessenger, codec: codec) if let api = api { voidVoidChannel.setMessageHandler { _, reply in api.voidVoid { result in @@ -3301,49 +3335,12 @@ class HostSmallApiSetup { } } } -private class FlutterSmallApiCodecReader: FlutterStandardReader { - override func readValue(ofType type: UInt8) -> Any? { - switch type { - case 128: - return TestMessage.fromList(self.readValue() as! [Any?]) - default: - return super.readValue(ofType: type) - } - } -} - -private class FlutterSmallApiCodecWriter: FlutterStandardWriter { - override func writeValue(_ value: Any) { - if let value = value as? TestMessage { - super.writeByte(128) - super.writeValue(value.toList()) - } else { - super.writeValue(value) - } - } -} - -private class FlutterSmallApiCodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return FlutterSmallApiCodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return FlutterSmallApiCodecWriter(data: data) - } -} - -class FlutterSmallApiCodec: FlutterStandardMessageCodec { - static let shared = FlutterSmallApiCodec(readerWriter: FlutterSmallApiCodecReaderWriter()) -} - /// A simple API called in some unit tests. /// /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol FlutterSmallApiProtocol { - func echo( - _ msgArg: TestMessage, completion: @escaping (Result) -> Void) - func echo(string aStringArg: String, completion: @escaping (Result) -> Void) + func echo(_ msgArg: TestMessage, completion: @escaping (Result) -> Void) + func echo(string aStringArg: String, completion: @escaping (Result) -> Void) } class FlutterSmallApi: FlutterSmallApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -3352,12 +3349,11 @@ class FlutterSmallApi: FlutterSmallApiProtocol { self.binaryMessenger = binaryMessenger self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" } - var codec: FlutterStandardMessageCodec { - return FlutterSmallApiCodec.shared + var codec: CoreTestsPigeonCodec { + return CoreTestsPigeonCodec.shared } - func echo( - _ msgArg: TestMessage, completion: @escaping (Result) -> Void - ) { + func echo(_ msgArg: TestMessage, completion: @escaping (Result) -> Void) + { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi.echoWrappedList\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( @@ -3371,11 +3367,11 @@ class FlutterSmallApi: FlutterSmallApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { @@ -3384,7 +3380,7 @@ class FlutterSmallApi: FlutterSmallApiProtocol { } } } - func echo(string aStringArg: String, completion: @escaping (Result) -> Void) + func echo(string aStringArg: String, completion: @escaping (Result) -> Void) { let channelName: String = "dev.flutter.pigeon.pigeon_integration_tests.FlutterSmallApi.echoString\(messageChannelSuffix)" @@ -3399,11 +3395,11 @@ class FlutterSmallApi: FlutterSmallApiProtocol { let code: String = listResponse[0] as! String let message: String? = nilOrValue(listResponse[1]) let details: String? = nilOrValue(listResponse[2]) - completion(.failure(FlutterError(code: code, message: message, details: details))) + completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { completion( .failure( - FlutterError( + PigeonError( code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift index 759b4ac21ac..5a649736641 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift @@ -5,8 +5,6 @@ import Cocoa import FlutterMacOS -extension FlutterError: Error {} - /// This plugin handles the native side of the integration tests in /// example/integration_test/. public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { @@ -49,15 +47,15 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func throwError() throws -> Any? { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } func throwErrorFromVoid() throws { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } func throwFlutterError() throws -> Any? { - throw FlutterError(code: "code", message: "message", details: "details") + throw PigeonError(code: "code", message: "message", details: "details") } func echo(_ anInt: Int64) -> Int64 { @@ -84,8 +82,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { return anObject } - func echo(_ aList: [Any?]) throws -> [Any?] { - return aList + func echo(_ list: [Any?]) throws -> [Any?] { + return list } func echo(_ aMap: [String?: Any?]) throws -> [String?: Any?] { @@ -185,15 +183,15 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func throwAsyncError(completion: @escaping (Result) -> Void) { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) } func throwAsyncErrorFromVoid(completion: @escaping (Result) -> Void) { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) } func throwAsyncFlutterError(completion: @escaping (Result) -> Void) { - completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + completion(.failure(PigeonError(code: "code", message: "message", details: "details"))) } func echoAsync(_ everything: AllTypes, completion: @escaping (Result) -> Void) { @@ -241,8 +239,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(anObject)) } - func echoAsync(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { - completion(.success(aList)) + func echoAsync(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { + completion(.success(list)) } func echoAsync( @@ -284,8 +282,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(anObject)) } - func echoAsyncNullable(_ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) { - completion(.success(aList)) + func echoAsyncNullable(_ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) { + completion(.success(list)) } func echoAsyncNullable( @@ -456,10 +454,10 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func callFlutterEcho( - _ aList: FlutterStandardTypedData, + _ list: FlutterStandardTypedData, completion: @escaping (Result) -> Void ) { - flutterAPI.echo(aList) { response in + flutterAPI.echo(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -469,8 +467,8 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } } - func callFlutterEcho(_ aList: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { - flutterAPI.echo(aList) { response in + func callFlutterEcho(_ list: [Any?], completion: @escaping (Result<[Any?], Error>) -> Void) { + flutterAPI.echo(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -556,10 +554,10 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func callFlutterEchoNullable( - _ aList: FlutterStandardTypedData?, + _ list: FlutterStandardTypedData?, completion: @escaping (Result) -> Void ) { - flutterAPI.echoNullable(aList) { response in + flutterAPI.echoNullable(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -570,9 +568,9 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } func callFlutterEchoNullable( - _ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void + _ list: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void ) { - flutterAPI.echoNullable(aList) { response in + flutterAPI.echoNullable(list) { response in switch response { case .success(let res): completion(.success(res)) @@ -622,7 +620,7 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { } else { completion( .failure( - FlutterError( + PigeonError( code: "", message: "Multi-instance responses were not matching: \(resOne), \(resTwo)", details: nil))) diff --git a/packages/pigeon/platform_tests/test_plugin/pubspec.yaml b/packages/pigeon/platform_tests/test_plugin/pubspec.yaml index 75b456336e8..a26b05c7eed 100644 --- a/packages/pigeon/platform_tests/test_plugin/pubspec.yaml +++ b/packages/pigeon/platform_tests/test_plugin/pubspec.yaml @@ -4,8 +4,8 @@ version: 0.0.1 publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/pigeon/platform_tests/test_plugin/windows/CMakeLists.txt b/packages/pigeon/platform_tests/test_plugin/windows/CMakeLists.txt index 30e26d2bb1b..e69e6bc7513 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/CMakeLists.txt +++ b/packages/pigeon/platform_tests/test_plugin/windows/CMakeLists.txt @@ -62,6 +62,10 @@ target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) +# Override apply_standard_settings for exceptions due to +# https://developercommunity.visualstudio.com/t/stdany-doesnt-link-when-exceptions-are-disabled/376072 +# TODO(stuartmorgan): Remove this once CI is using VS 2022 or later. +target_compile_definitions(${PLUGIN_NAME} PRIVATE "_HAS_EXCEPTIONS=1") # List of absolute paths to libraries that should be bundled with the plugin. # This list could contain prebuilt libraries, or libraries created by an @@ -118,6 +122,10 @@ add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_LIBRARY}" $ ) +# Override apply_standard_settings for exceptions due to +# https://developercommunity.visualstudio.com/t/stdany-doesnt-link-when-exceptions-are-disabled/376072 +# TODO(stuartmorgan): Remove this once CI is using VS 2022 or later. +target_compile_definitions(${TEST_RUNNER} PRIVATE "_HAS_EXCEPTIONS=1") include(GoogleTest) gtest_discover_tests(${TEST_RUNNER}) diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp index a1cff580e06..c97dc064a96 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp @@ -39,9 +39,12 @@ AllTypes::AllTypes(bool a_bool, int64_t an_int, int64_t an_int64, const std::vector& a4_byte_array, const std::vector& a8_byte_array, const std::vector& a_float_array, - const EncodableList& a_list, const EncodableMap& a_map, const AnEnum& an_enum, const std::string& a_string, - const EncodableValue& an_object) + const EncodableValue& an_object, const EncodableList& list, + const EncodableList& string_list, + const EncodableList& int_list, + const EncodableList& double_list, + const EncodableList& bool_list, const EncodableMap& map) : a_bool_(a_bool), an_int_(an_int), an_int64_(an_int64), @@ -50,11 +53,15 @@ AllTypes::AllTypes(bool a_bool, int64_t an_int, int64_t an_int64, a4_byte_array_(a4_byte_array), a8_byte_array_(a8_byte_array), a_float_array_(a_float_array), - a_list_(a_list), - a_map_(a_map), an_enum_(an_enum), a_string_(a_string), - an_object_(an_object) {} + an_object_(an_object), + list_(list), + string_list_(string_list), + int_list_(int_list), + double_list_(double_list), + bool_list_(bool_list), + map_(map) {} bool AllTypes::a_bool() const { return a_bool_; } @@ -104,16 +111,6 @@ void AllTypes::set_a_float_array(const std::vector& value_arg) { a_float_array_ = value_arg; } -const EncodableList& AllTypes::a_list() const { return a_list_; } - -void AllTypes::set_a_list(const EncodableList& value_arg) { - a_list_ = value_arg; -} - -const EncodableMap& AllTypes::a_map() const { return a_map_; } - -void AllTypes::set_a_map(const EncodableMap& value_arg) { a_map_ = value_arg; } - const AnEnum& AllTypes::an_enum() const { return an_enum_; } void AllTypes::set_an_enum(const AnEnum& value_arg) { an_enum_ = value_arg; } @@ -130,9 +127,41 @@ void AllTypes::set_an_object(const EncodableValue& value_arg) { an_object_ = value_arg; } +const EncodableList& AllTypes::list() const { return list_; } + +void AllTypes::set_list(const EncodableList& value_arg) { list_ = value_arg; } + +const EncodableList& AllTypes::string_list() const { return string_list_; } + +void AllTypes::set_string_list(const EncodableList& value_arg) { + string_list_ = value_arg; +} + +const EncodableList& AllTypes::int_list() const { return int_list_; } + +void AllTypes::set_int_list(const EncodableList& value_arg) { + int_list_ = value_arg; +} + +const EncodableList& AllTypes::double_list() const { return double_list_; } + +void AllTypes::set_double_list(const EncodableList& value_arg) { + double_list_ = value_arg; +} + +const EncodableList& AllTypes::bool_list() const { return bool_list_; } + +void AllTypes::set_bool_list(const EncodableList& value_arg) { + bool_list_ = value_arg; +} + +const EncodableMap& AllTypes::map() const { return map_; } + +void AllTypes::set_map(const EncodableMap& value_arg) { map_ = value_arg; } + EncodableList AllTypes::ToEncodableList() const { EncodableList list; - list.reserve(13); + list.reserve(17); list.push_back(EncodableValue(a_bool_)); list.push_back(EncodableValue(an_int_)); list.push_back(EncodableValue(an_int64_)); @@ -141,11 +170,15 @@ EncodableList AllTypes::ToEncodableList() const { list.push_back(EncodableValue(a4_byte_array_)); list.push_back(EncodableValue(a8_byte_array_)); list.push_back(EncodableValue(a_float_array_)); - list.push_back(EncodableValue(a_list_)); - list.push_back(EncodableValue(a_map_)); - list.push_back(EncodableValue((int)an_enum_)); + list.push_back(CustomEncodableValue(an_enum_)); list.push_back(EncodableValue(a_string_)); list.push_back(an_object_); + list.push_back(EncodableValue(list_)); + list.push_back(EncodableValue(string_list_)); + list.push_back(EncodableValue(int_list_)); + list.push_back(EncodableValue(double_list_)); + list.push_back(EncodableValue(bool_list_)); + list.push_back(EncodableValue(map_)); return list; } @@ -155,9 +188,12 @@ AllTypes AllTypes::FromEncodableList(const EncodableList& list) { std::get(list[3]), std::get>(list[4]), std::get>(list[5]), std::get>(list[6]), - std::get>(list[7]), std::get(list[8]), - std::get(list[9]), (AnEnum)(std::get(list[10])), - std::get(list[11]), list[12]); + std::get>(list[7]), + std::any_cast(std::get(list[8])), + std::get(list[9]), list[10], + std::get(list[11]), std::get(list[12]), + std::get(list[13]), std::get(list[14]), + std::get(list[15]), std::get(list[16])); return decoded; } @@ -172,13 +208,15 @@ AllNullableTypes::AllNullableTypes( const std::vector* a_nullable4_byte_array, const std::vector* a_nullable8_byte_array, const std::vector* a_nullable_float_array, - const EncodableList* a_nullable_list, const EncodableMap* a_nullable_map, const EncodableList* nullable_nested_list, const EncodableMap* nullable_map_with_annotations, const EncodableMap* nullable_map_with_object, const AnEnum* a_nullable_enum, const std::string* a_nullable_string, const EncodableValue* a_nullable_object, - const AllNullableTypes* all_nullable_types) + const AllNullableTypes* all_nullable_types, const EncodableList* list, + const EncodableList* string_list, const EncodableList* int_list, + const EncodableList* double_list, const EncodableList* bool_list, + const EncodableList* nested_class_list, const EncodableMap* map) : a_nullable_bool_(a_nullable_bool ? std::optional(*a_nullable_bool) : std::nullopt), a_nullable_int_(a_nullable_int ? std::optional(*a_nullable_int) @@ -205,12 +243,6 @@ AllNullableTypes::AllNullableTypes( a_nullable_float_array ? std::optional>(*a_nullable_float_array) : std::nullopt), - a_nullable_list_(a_nullable_list - ? std::optional(*a_nullable_list) - : std::nullopt), - a_nullable_map_(a_nullable_map - ? std::optional(*a_nullable_map) - : std::nullopt), nullable_nested_list_(nullable_nested_list ? std::optional( *nullable_nested_list) : std::nullopt), @@ -233,7 +265,20 @@ AllNullableTypes::AllNullableTypes( all_nullable_types_( all_nullable_types ? std::make_unique(*all_nullable_types) - : nullptr) {} + : nullptr), + list_(list ? std::optional(*list) : std::nullopt), + string_list_(string_list ? std::optional(*string_list) + : std::nullopt), + int_list_(int_list ? std::optional(*int_list) + : std::nullopt), + double_list_(double_list ? std::optional(*double_list) + : std::nullopt), + bool_list_(bool_list ? std::optional(*bool_list) + : std::nullopt), + nested_class_list_(nested_class_list + ? std::optional(*nested_class_list) + : std::nullopt), + map_(map ? std::optional(*map) : std::nullopt) {} AllNullableTypes::AllNullableTypes(const AllNullableTypes& other) : a_nullable_bool_(other.a_nullable_bool_ @@ -264,12 +309,6 @@ AllNullableTypes::AllNullableTypes(const AllNullableTypes& other) ? std::optional>( *other.a_nullable_float_array_) : std::nullopt), - a_nullable_list_(other.a_nullable_list_ ? std::optional( - *other.a_nullable_list_) - : std::nullopt), - a_nullable_map_(other.a_nullable_map_ - ? std::optional(*other.a_nullable_map_) - : std::nullopt), nullable_nested_list_( other.nullable_nested_list_ ? std::optional(*other.nullable_nested_list_) @@ -297,7 +336,26 @@ AllNullableTypes::AllNullableTypes(const AllNullableTypes& other) all_nullable_types_( other.all_nullable_types_ ? std::make_unique(*other.all_nullable_types_) - : nullptr) {} + : nullptr), + list_(other.list_ ? std::optional(*other.list_) + : std::nullopt), + string_list_(other.string_list_ + ? std::optional(*other.string_list_) + : std::nullopt), + int_list_(other.int_list_ ? std::optional(*other.int_list_) + : std::nullopt), + double_list_(other.double_list_ + ? std::optional(*other.double_list_) + : std::nullopt), + bool_list_(other.bool_list_ + ? std::optional(*other.bool_list_) + : std::nullopt), + nested_class_list_( + other.nested_class_list_ + ? std::optional(*other.nested_class_list_) + : std::nullopt), + map_(other.map_ ? std::optional(*other.map_) + : std::nullopt) {} AllNullableTypes& AllNullableTypes::operator=(const AllNullableTypes& other) { a_nullable_bool_ = other.a_nullable_bool_; @@ -308,8 +366,6 @@ AllNullableTypes& AllNullableTypes::operator=(const AllNullableTypes& other) { a_nullable4_byte_array_ = other.a_nullable4_byte_array_; a_nullable8_byte_array_ = other.a_nullable8_byte_array_; a_nullable_float_array_ = other.a_nullable_float_array_; - a_nullable_list_ = other.a_nullable_list_; - a_nullable_map_ = other.a_nullable_map_; nullable_nested_list_ = other.nullable_nested_list_; nullable_map_with_annotations_ = other.nullable_map_with_annotations_; nullable_map_with_object_ = other.nullable_map_with_object_; @@ -320,6 +376,13 @@ AllNullableTypes& AllNullableTypes::operator=(const AllNullableTypes& other) { other.all_nullable_types_ ? std::make_unique(*other.all_nullable_types_) : nullptr; + list_ = other.list_; + string_list_ = other.string_list_; + int_list_ = other.int_list_; + double_list_ = other.double_list_; + bool_list_ = other.bool_list_; + nested_class_list_ = other.nested_class_list_; + map_ = other.map_; return *this; } @@ -437,32 +500,6 @@ void AllNullableTypes::set_a_nullable_float_array( a_nullable_float_array_ = value_arg; } -const EncodableList* AllNullableTypes::a_nullable_list() const { - return a_nullable_list_ ? &(*a_nullable_list_) : nullptr; -} - -void AllNullableTypes::set_a_nullable_list(const EncodableList* value_arg) { - a_nullable_list_ = - value_arg ? std::optional(*value_arg) : std::nullopt; -} - -void AllNullableTypes::set_a_nullable_list(const EncodableList& value_arg) { - a_nullable_list_ = value_arg; -} - -const EncodableMap* AllNullableTypes::a_nullable_map() const { - return a_nullable_map_ ? &(*a_nullable_map_) : nullptr; -} - -void AllNullableTypes::set_a_nullable_map(const EncodableMap* value_arg) { - a_nullable_map_ = - value_arg ? std::optional(*value_arg) : std::nullopt; -} - -void AllNullableTypes::set_a_nullable_map(const EncodableMap& value_arg) { - a_nullable_map_ = value_arg; -} - const EncodableList* AllNullableTypes::nullable_nested_list() const { return nullable_nested_list_ ? &(*nullable_nested_list_) : nullptr; } @@ -564,9 +601,98 @@ void AllNullableTypes::set_all_nullable_types( all_nullable_types_ = std::make_unique(value_arg); } +const EncodableList* AllNullableTypes::list() const { + return list_ ? &(*list_) : nullptr; +} + +void AllNullableTypes::set_list(const EncodableList* value_arg) { + list_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_list(const EncodableList& value_arg) { + list_ = value_arg; +} + +const EncodableList* AllNullableTypes::string_list() const { + return string_list_ ? &(*string_list_) : nullptr; +} + +void AllNullableTypes::set_string_list(const EncodableList* value_arg) { + string_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_string_list(const EncodableList& value_arg) { + string_list_ = value_arg; +} + +const EncodableList* AllNullableTypes::int_list() const { + return int_list_ ? &(*int_list_) : nullptr; +} + +void AllNullableTypes::set_int_list(const EncodableList* value_arg) { + int_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_int_list(const EncodableList& value_arg) { + int_list_ = value_arg; +} + +const EncodableList* AllNullableTypes::double_list() const { + return double_list_ ? &(*double_list_) : nullptr; +} + +void AllNullableTypes::set_double_list(const EncodableList* value_arg) { + double_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_double_list(const EncodableList& value_arg) { + double_list_ = value_arg; +} + +const EncodableList* AllNullableTypes::bool_list() const { + return bool_list_ ? &(*bool_list_) : nullptr; +} + +void AllNullableTypes::set_bool_list(const EncodableList* value_arg) { + bool_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_bool_list(const EncodableList& value_arg) { + bool_list_ = value_arg; +} + +const EncodableList* AllNullableTypes::nested_class_list() const { + return nested_class_list_ ? &(*nested_class_list_) : nullptr; +} + +void AllNullableTypes::set_nested_class_list(const EncodableList* value_arg) { + nested_class_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_nested_class_list(const EncodableList& value_arg) { + nested_class_list_ = value_arg; +} + +const EncodableMap* AllNullableTypes::map() const { + return map_ ? &(*map_) : nullptr; +} + +void AllNullableTypes::set_map(const EncodableMap* value_arg) { + map_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypes::set_map(const EncodableMap& value_arg) { + map_ = value_arg; +} + EncodableList AllNullableTypes::ToEncodableList() const { EncodableList list; - list.reserve(17); + list.reserve(22); list.push_back(a_nullable_bool_ ? EncodableValue(*a_nullable_bool_) : EncodableValue()); list.push_back(a_nullable_int_ ? EncodableValue(*a_nullable_int_) @@ -587,10 +713,6 @@ EncodableList AllNullableTypes::ToEncodableList() const { list.push_back(a_nullable_float_array_ ? EncodableValue(*a_nullable_float_array_) : EncodableValue()); - list.push_back(a_nullable_list_ ? EncodableValue(*a_nullable_list_) - : EncodableValue()); - list.push_back(a_nullable_map_ ? EncodableValue(*a_nullable_map_) - : EncodableValue()); list.push_back(nullable_nested_list_ ? EncodableValue(*nullable_nested_list_) : EncodableValue()); list.push_back(nullable_map_with_annotations_ @@ -599,14 +721,24 @@ EncodableList AllNullableTypes::ToEncodableList() const { list.push_back(nullable_map_with_object_ ? EncodableValue(*nullable_map_with_object_) : EncodableValue()); - list.push_back(a_nullable_enum_ ? EncodableValue((int)(*a_nullable_enum_)) + list.push_back(a_nullable_enum_ ? CustomEncodableValue(*a_nullable_enum_) : EncodableValue()); list.push_back(a_nullable_string_ ? EncodableValue(*a_nullable_string_) : EncodableValue()); list.push_back(a_nullable_object_ ? *a_nullable_object_ : EncodableValue()); list.push_back(all_nullable_types_ - ? EncodableValue(all_nullable_types_->ToEncodableList()) + ? CustomEncodableValue(*all_nullable_types_) : EncodableValue()); + list.push_back(list_ ? EncodableValue(*list_) : EncodableValue()); + list.push_back(string_list_ ? EncodableValue(*string_list_) + : EncodableValue()); + list.push_back(int_list_ ? EncodableValue(*int_list_) : EncodableValue()); + list.push_back(double_list_ ? EncodableValue(*double_list_) + : EncodableValue()); + list.push_back(bool_list_ ? EncodableValue(*bool_list_) : EncodableValue()); + list.push_back(nested_class_list_ ? EncodableValue(*nested_class_list_) + : EncodableValue()); + list.push_back(map_ ? EncodableValue(*map_) : EncodableValue()); return list; } @@ -650,49 +782,68 @@ AllNullableTypes AllNullableTypes::FromEncodableList( decoded.set_a_nullable_float_array( std::get>(encodable_a_nullable_float_array)); } - auto& encodable_a_nullable_list = list[8]; - if (!encodable_a_nullable_list.IsNull()) { - decoded.set_a_nullable_list( - std::get(encodable_a_nullable_list)); - } - auto& encodable_a_nullable_map = list[9]; - if (!encodable_a_nullable_map.IsNull()) { - decoded.set_a_nullable_map( - std::get(encodable_a_nullable_map)); - } - auto& encodable_nullable_nested_list = list[10]; + auto& encodable_nullable_nested_list = list[8]; if (!encodable_nullable_nested_list.IsNull()) { decoded.set_nullable_nested_list( std::get(encodable_nullable_nested_list)); } - auto& encodable_nullable_map_with_annotations = list[11]; + auto& encodable_nullable_map_with_annotations = list[9]; if (!encodable_nullable_map_with_annotations.IsNull()) { decoded.set_nullable_map_with_annotations( std::get(encodable_nullable_map_with_annotations)); } - auto& encodable_nullable_map_with_object = list[12]; + auto& encodable_nullable_map_with_object = list[10]; if (!encodable_nullable_map_with_object.IsNull()) { decoded.set_nullable_map_with_object( std::get(encodable_nullable_map_with_object)); } - auto& encodable_a_nullable_enum = list[13]; + auto& encodable_a_nullable_enum = list[11]; if (!encodable_a_nullable_enum.IsNull()) { - decoded.set_a_nullable_enum( - (AnEnum)(std::get(encodable_a_nullable_enum))); + decoded.set_a_nullable_enum(std::any_cast( + std::get(encodable_a_nullable_enum))); } - auto& encodable_a_nullable_string = list[14]; + auto& encodable_a_nullable_string = list[12]; if (!encodable_a_nullable_string.IsNull()) { decoded.set_a_nullable_string( std::get(encodable_a_nullable_string)); } - auto& encodable_a_nullable_object = list[15]; + auto& encodable_a_nullable_object = list[13]; if (!encodable_a_nullable_object.IsNull()) { decoded.set_a_nullable_object(encodable_a_nullable_object); } - auto& encodable_all_nullable_types = list[16]; + auto& encodable_all_nullable_types = list[14]; if (!encodable_all_nullable_types.IsNull()) { - decoded.set_all_nullable_types(AllNullableTypes::FromEncodableList( - std::get(encodable_all_nullable_types))); + decoded.set_all_nullable_types(std::any_cast( + std::get(encodable_all_nullable_types))); + } + auto& encodable_list = list[15]; + if (!encodable_list.IsNull()) { + decoded.set_list(std::get(encodable_list)); + } + auto& encodable_string_list = list[16]; + if (!encodable_string_list.IsNull()) { + decoded.set_string_list(std::get(encodable_string_list)); + } + auto& encodable_int_list = list[17]; + if (!encodable_int_list.IsNull()) { + decoded.set_int_list(std::get(encodable_int_list)); + } + auto& encodable_double_list = list[18]; + if (!encodable_double_list.IsNull()) { + decoded.set_double_list(std::get(encodable_double_list)); + } + auto& encodable_bool_list = list[19]; + if (!encodable_bool_list.IsNull()) { + decoded.set_bool_list(std::get(encodable_bool_list)); + } + auto& encodable_nested_class_list = list[20]; + if (!encodable_nested_class_list.IsNull()) { + decoded.set_nested_class_list( + std::get(encodable_nested_class_list)); + } + auto& encodable_map = list[21]; + if (!encodable_map.IsNull()) { + decoded.set_map(std::get(encodable_map)); } return decoded; } @@ -708,12 +859,14 @@ AllNullableTypesWithoutRecursion::AllNullableTypesWithoutRecursion( const std::vector* a_nullable4_byte_array, const std::vector* a_nullable8_byte_array, const std::vector* a_nullable_float_array, - const EncodableList* a_nullable_list, const EncodableMap* a_nullable_map, const EncodableList* nullable_nested_list, const EncodableMap* nullable_map_with_annotations, const EncodableMap* nullable_map_with_object, const AnEnum* a_nullable_enum, const std::string* a_nullable_string, - const EncodableValue* a_nullable_object) + const EncodableValue* a_nullable_object, const EncodableList* list, + const EncodableList* string_list, const EncodableList* int_list, + const EncodableList* double_list, const EncodableList* bool_list, + const EncodableMap* map) : a_nullable_bool_(a_nullable_bool ? std::optional(*a_nullable_bool) : std::nullopt), a_nullable_int_(a_nullable_int ? std::optional(*a_nullable_int) @@ -740,12 +893,6 @@ AllNullableTypesWithoutRecursion::AllNullableTypesWithoutRecursion( a_nullable_float_array ? std::optional>(*a_nullable_float_array) : std::nullopt), - a_nullable_list_(a_nullable_list - ? std::optional(*a_nullable_list) - : std::nullopt), - a_nullable_map_(a_nullable_map - ? std::optional(*a_nullable_map) - : std::nullopt), nullable_nested_list_(nullable_nested_list ? std::optional( *nullable_nested_list) : std::nullopt), @@ -764,7 +911,17 @@ AllNullableTypesWithoutRecursion::AllNullableTypesWithoutRecursion( : std::nullopt), a_nullable_object_(a_nullable_object ? std::optional(*a_nullable_object) - : std::nullopt) {} + : std::nullopt), + list_(list ? std::optional(*list) : std::nullopt), + string_list_(string_list ? std::optional(*string_list) + : std::nullopt), + int_list_(int_list ? std::optional(*int_list) + : std::nullopt), + double_list_(double_list ? std::optional(*double_list) + : std::nullopt), + bool_list_(bool_list ? std::optional(*bool_list) + : std::nullopt), + map_(map ? std::optional(*map) : std::nullopt) {} const bool* AllNullableTypesWithoutRecursion::a_nullable_bool() const { return a_nullable_bool_ ? &(*a_nullable_bool_) : nullptr; @@ -888,36 +1045,6 @@ void AllNullableTypesWithoutRecursion::set_a_nullable_float_array( a_nullable_float_array_ = value_arg; } -const EncodableList* AllNullableTypesWithoutRecursion::a_nullable_list() const { - return a_nullable_list_ ? &(*a_nullable_list_) : nullptr; -} - -void AllNullableTypesWithoutRecursion::set_a_nullable_list( - const EncodableList* value_arg) { - a_nullable_list_ = - value_arg ? std::optional(*value_arg) : std::nullopt; -} - -void AllNullableTypesWithoutRecursion::set_a_nullable_list( - const EncodableList& value_arg) { - a_nullable_list_ = value_arg; -} - -const EncodableMap* AllNullableTypesWithoutRecursion::a_nullable_map() const { - return a_nullable_map_ ? &(*a_nullable_map_) : nullptr; -} - -void AllNullableTypesWithoutRecursion::set_a_nullable_map( - const EncodableMap* value_arg) { - a_nullable_map_ = - value_arg ? std::optional(*value_arg) : std::nullopt; -} - -void AllNullableTypesWithoutRecursion::set_a_nullable_map( - const EncodableMap& value_arg) { - a_nullable_map_ = value_arg; -} - const EncodableList* AllNullableTypesWithoutRecursion::nullable_nested_list() const { return nullable_nested_list_ ? &(*nullable_nested_list_) : nullptr; @@ -1013,9 +1140,95 @@ void AllNullableTypesWithoutRecursion::set_a_nullable_object( a_nullable_object_ = value_arg; } +const EncodableList* AllNullableTypesWithoutRecursion::list() const { + return list_ ? &(*list_) : nullptr; +} + +void AllNullableTypesWithoutRecursion::set_list( + const EncodableList* value_arg) { + list_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypesWithoutRecursion::set_list( + const EncodableList& value_arg) { + list_ = value_arg; +} + +const EncodableList* AllNullableTypesWithoutRecursion::string_list() const { + return string_list_ ? &(*string_list_) : nullptr; +} + +void AllNullableTypesWithoutRecursion::set_string_list( + const EncodableList* value_arg) { + string_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypesWithoutRecursion::set_string_list( + const EncodableList& value_arg) { + string_list_ = value_arg; +} + +const EncodableList* AllNullableTypesWithoutRecursion::int_list() const { + return int_list_ ? &(*int_list_) : nullptr; +} + +void AllNullableTypesWithoutRecursion::set_int_list( + const EncodableList* value_arg) { + int_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypesWithoutRecursion::set_int_list( + const EncodableList& value_arg) { + int_list_ = value_arg; +} + +const EncodableList* AllNullableTypesWithoutRecursion::double_list() const { + return double_list_ ? &(*double_list_) : nullptr; +} + +void AllNullableTypesWithoutRecursion::set_double_list( + const EncodableList* value_arg) { + double_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypesWithoutRecursion::set_double_list( + const EncodableList& value_arg) { + double_list_ = value_arg; +} + +const EncodableList* AllNullableTypesWithoutRecursion::bool_list() const { + return bool_list_ ? &(*bool_list_) : nullptr; +} + +void AllNullableTypesWithoutRecursion::set_bool_list( + const EncodableList* value_arg) { + bool_list_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypesWithoutRecursion::set_bool_list( + const EncodableList& value_arg) { + bool_list_ = value_arg; +} + +const EncodableMap* AllNullableTypesWithoutRecursion::map() const { + return map_ ? &(*map_) : nullptr; +} + +void AllNullableTypesWithoutRecursion::set_map(const EncodableMap* value_arg) { + map_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AllNullableTypesWithoutRecursion::set_map(const EncodableMap& value_arg) { + map_ = value_arg; +} + EncodableList AllNullableTypesWithoutRecursion::ToEncodableList() const { EncodableList list; - list.reserve(16); + list.reserve(20); list.push_back(a_nullable_bool_ ? EncodableValue(*a_nullable_bool_) : EncodableValue()); list.push_back(a_nullable_int_ ? EncodableValue(*a_nullable_int_) @@ -1036,10 +1249,6 @@ EncodableList AllNullableTypesWithoutRecursion::ToEncodableList() const { list.push_back(a_nullable_float_array_ ? EncodableValue(*a_nullable_float_array_) : EncodableValue()); - list.push_back(a_nullable_list_ ? EncodableValue(*a_nullable_list_) - : EncodableValue()); - list.push_back(a_nullable_map_ ? EncodableValue(*a_nullable_map_) - : EncodableValue()); list.push_back(nullable_nested_list_ ? EncodableValue(*nullable_nested_list_) : EncodableValue()); list.push_back(nullable_map_with_annotations_ @@ -1048,11 +1257,19 @@ EncodableList AllNullableTypesWithoutRecursion::ToEncodableList() const { list.push_back(nullable_map_with_object_ ? EncodableValue(*nullable_map_with_object_) : EncodableValue()); - list.push_back(a_nullable_enum_ ? EncodableValue((int)(*a_nullable_enum_)) + list.push_back(a_nullable_enum_ ? CustomEncodableValue(*a_nullable_enum_) : EncodableValue()); list.push_back(a_nullable_string_ ? EncodableValue(*a_nullable_string_) : EncodableValue()); list.push_back(a_nullable_object_ ? *a_nullable_object_ : EncodableValue()); + list.push_back(list_ ? EncodableValue(*list_) : EncodableValue()); + list.push_back(string_list_ ? EncodableValue(*string_list_) + : EncodableValue()); + list.push_back(int_list_ ? EncodableValue(*int_list_) : EncodableValue()); + list.push_back(double_list_ ? EncodableValue(*double_list_) + : EncodableValue()); + list.push_back(bool_list_ ? EncodableValue(*bool_list_) : EncodableValue()); + list.push_back(map_ ? EncodableValue(*map_) : EncodableValue()); return list; } @@ -1096,45 +1313,59 @@ AllNullableTypesWithoutRecursion::FromEncodableList(const EncodableList& list) { decoded.set_a_nullable_float_array( std::get>(encodable_a_nullable_float_array)); } - auto& encodable_a_nullable_list = list[8]; - if (!encodable_a_nullable_list.IsNull()) { - decoded.set_a_nullable_list( - std::get(encodable_a_nullable_list)); - } - auto& encodable_a_nullable_map = list[9]; - if (!encodable_a_nullable_map.IsNull()) { - decoded.set_a_nullable_map( - std::get(encodable_a_nullable_map)); - } - auto& encodable_nullable_nested_list = list[10]; + auto& encodable_nullable_nested_list = list[8]; if (!encodable_nullable_nested_list.IsNull()) { decoded.set_nullable_nested_list( std::get(encodable_nullable_nested_list)); } - auto& encodable_nullable_map_with_annotations = list[11]; + auto& encodable_nullable_map_with_annotations = list[9]; if (!encodable_nullable_map_with_annotations.IsNull()) { decoded.set_nullable_map_with_annotations( std::get(encodable_nullable_map_with_annotations)); } - auto& encodable_nullable_map_with_object = list[12]; + auto& encodable_nullable_map_with_object = list[10]; if (!encodable_nullable_map_with_object.IsNull()) { decoded.set_nullable_map_with_object( std::get(encodable_nullable_map_with_object)); } - auto& encodable_a_nullable_enum = list[13]; + auto& encodable_a_nullable_enum = list[11]; if (!encodable_a_nullable_enum.IsNull()) { - decoded.set_a_nullable_enum( - (AnEnum)(std::get(encodable_a_nullable_enum))); + decoded.set_a_nullable_enum(std::any_cast( + std::get(encodable_a_nullable_enum))); } - auto& encodable_a_nullable_string = list[14]; + auto& encodable_a_nullable_string = list[12]; if (!encodable_a_nullable_string.IsNull()) { decoded.set_a_nullable_string( std::get(encodable_a_nullable_string)); } - auto& encodable_a_nullable_object = list[15]; + auto& encodable_a_nullable_object = list[13]; if (!encodable_a_nullable_object.IsNull()) { decoded.set_a_nullable_object(encodable_a_nullable_object); } + auto& encodable_list = list[14]; + if (!encodable_list.IsNull()) { + decoded.set_list(std::get(encodable_list)); + } + auto& encodable_string_list = list[15]; + if (!encodable_string_list.IsNull()) { + decoded.set_string_list(std::get(encodable_string_list)); + } + auto& encodable_int_list = list[16]; + if (!encodable_int_list.IsNull()) { + decoded.set_int_list(std::get(encodable_int_list)); + } + auto& encodable_double_list = list[17]; + if (!encodable_double_list.IsNull()) { + decoded.set_double_list(std::get(encodable_double_list)); + } + auto& encodable_bool_list = list[18]; + if (!encodable_bool_list.IsNull()) { + decoded.set_bool_list(std::get(encodable_bool_list)); + } + auto& encodable_map = list[19]; + if (!encodable_map.IsNull()) { + decoded.set_map(std::get(encodable_map)); + } return decoded; } @@ -1226,32 +1457,31 @@ void AllClassesWrapper::set_all_types(const AllTypes& value_arg) { EncodableList AllClassesWrapper::ToEncodableList() const { EncodableList list; list.reserve(3); - list.push_back(EncodableValue(all_nullable_types_->ToEncodableList())); + list.push_back(CustomEncodableValue(*all_nullable_types_)); list.push_back( all_nullable_types_without_recursion_ - ? EncodableValue( - all_nullable_types_without_recursion_->ToEncodableList()) + ? CustomEncodableValue(*all_nullable_types_without_recursion_) : EncodableValue()); - list.push_back(all_types_ ? EncodableValue(all_types_->ToEncodableList()) + list.push_back(all_types_ ? CustomEncodableValue(*all_types_) : EncodableValue()); return list; } AllClassesWrapper AllClassesWrapper::FromEncodableList( const EncodableList& list) { - AllClassesWrapper decoded( - AllNullableTypes::FromEncodableList(std::get(list[0]))); + AllClassesWrapper decoded(std::any_cast( + std::get(list[0]))); auto& encodable_all_nullable_types_without_recursion = list[1]; if (!encodable_all_nullable_types_without_recursion.IsNull()) { decoded.set_all_nullable_types_without_recursion( - AllNullableTypesWithoutRecursion::FromEncodableList( - std::get( + std::any_cast( + std::get( encodable_all_nullable_types_without_recursion))); } auto& encodable_all_types = list[2]; if (!encodable_all_types.IsNull()) { - decoded.set_all_types(AllTypes::FromEncodableList( - std::get(encodable_all_types))); + decoded.set_all_types(std::any_cast( + std::get(encodable_all_types))); } return decoded; } @@ -1293,46 +1523,53 @@ TestMessage TestMessage::FromEncodableList(const EncodableList& list) { return decoded; } -HostIntegrationCoreApiCodecSerializer::HostIntegrationCoreApiCodecSerializer() { -} +PigeonCodecSerializer::PigeonCodecSerializer() {} -EncodableValue HostIntegrationCoreApiCodecSerializer::ReadValueOfType( +EncodableValue PigeonCodecSerializer::ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const { switch (type) { - case 128: - return CustomEncodableValue(AllClassesWrapper::FromEncodableList( - std::get(ReadValue(stream)))); case 129: - return CustomEncodableValue(AllNullableTypes::FromEncodableList( + return CustomEncodableValue(AllTypes::FromEncodableList( std::get(ReadValue(stream)))); case 130: + return CustomEncodableValue(AllNullableTypes::FromEncodableList( + std::get(ReadValue(stream)))); + case 131: return CustomEncodableValue( AllNullableTypesWithoutRecursion::FromEncodableList( std::get(ReadValue(stream)))); - case 131: - return CustomEncodableValue(AllTypes::FromEncodableList( - std::get(ReadValue(stream)))); case 132: + return CustomEncodableValue(AllClassesWrapper::FromEncodableList( + std::get(ReadValue(stream)))); + case 133: return CustomEncodableValue(TestMessage::FromEncodableList( std::get(ReadValue(stream)))); + case 134: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = + encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() + ? EncodableValue() + : CustomEncodableValue(static_cast(enum_arg_value)); + } default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } } -void HostIntegrationCoreApiCodecSerializer::WriteValue( +void PigeonCodecSerializer::WriteValue( const EncodableValue& value, flutter::ByteStreamWriter* stream) const { if (const CustomEncodableValue* custom_value = std::get_if(&value)) { - if (custom_value->type() == typeid(AllClassesWrapper)) { - stream->WriteByte(128); - WriteValue(EncodableValue(std::any_cast(*custom_value) - .ToEncodableList()), + if (custom_value->type() == typeid(AllTypes)) { + stream->WriteByte(129); + WriteValue(EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(AllNullableTypes)) { - stream->WriteByte(129); + stream->WriteByte(130); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -1340,28 +1577,35 @@ void HostIntegrationCoreApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(AllNullableTypesWithoutRecursion)) { - stream->WriteByte(130); + stream->WriteByte(131); WriteValue(EncodableValue(std::any_cast( *custom_value) .ToEncodableList()), stream); return; } - if (custom_value->type() == typeid(AllTypes)) { - stream->WriteByte(131); - WriteValue(EncodableValue( - std::any_cast(*custom_value).ToEncodableList()), + if (custom_value->type() == typeid(AllClassesWrapper)) { + stream->WriteByte(132); + WriteValue(EncodableValue(std::any_cast(*custom_value) + .ToEncodableList()), stream); return; } if (custom_value->type() == typeid(TestMessage)) { - stream->WriteByte(132); + stream->WriteByte(133); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), stream); return; } + if (custom_value->type() == typeid(AnEnum)) { + stream->WriteByte(134); + WriteValue(EncodableValue( + static_cast(std::any_cast(*custom_value))), + stream); + return; + } } flutter::StandardCodecSerializer::WriteValue(value, stream); } @@ -1369,7 +1613,7 @@ void HostIntegrationCoreApiCodecSerializer::WriteValue( /// The codec used by HostIntegrationCoreApi. const flutter::StandardMessageCodec& HostIntegrationCoreApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &HostIntegrationCoreApiCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } // Sets up an instance of `HostIntegrationCoreApi` to handle messages through @@ -1763,14 +2007,14 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - if (encodable_a_list_arg.IsNull()) { - reply(WrapError("a_list_arg unexpectedly null.")); + const auto& encodable_list_arg = args.at(0); + if (encodable_list_arg.IsNull()) { + reply(WrapError("list_arg unexpectedly null.")); return; } - const auto& a_list_arg = - std::get(encodable_a_list_arg); - ErrorOr output = api->EchoList(a_list_arg); + const auto& list_arg = + std::get(encodable_list_arg); + ErrorOr output = api->EchoList(list_arg); if (output.has_error()) { reply(WrapError(output.error())); return; @@ -1875,8 +2119,8 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, reply(WrapError("an_enum_arg unexpectedly null.")); return; } - const AnEnum& an_enum_arg = - (AnEnum)encodable_an_enum_arg.LongValue(); + const auto& an_enum_arg = std::any_cast( + std::get(encodable_an_enum_arg)); ErrorOr output = api->EchoEnum(an_enum_arg); if (output.has_error()) { reply(WrapError(output.error())); @@ -1884,7 +2128,7 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } EncodableList wrapped; wrapped.push_back( - EncodableValue((int)std::move(output).TakeValue())); + CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -2017,9 +2261,11 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& args = std::get(message); const auto& encodable_everything_arg = args.at(0); const auto* everything_arg = - &(std::any_cast( - std::get( - encodable_everything_arg))); + encodable_everything_arg.IsNull() + ? nullptr + : &(std::any_cast( + std::get( + encodable_everything_arg))); ErrorOr> output = api->EchoAllNullableTypes(everything_arg); if (output.has_error()) { @@ -2051,35 +2297,38 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler( - [api](const EncodableValue& message, - const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_everything_arg = args.at(0); - const auto* everything_arg = - &(std::any_cast( - std::get( - encodable_everything_arg))); - ErrorOr> output = - api->EchoAllNullableTypesWithoutRecursion(everything_arg); - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - auto output_optional = std::move(output).TakeValue(); - if (output_optional) { - wrapped.push_back( - CustomEncodableValue(std::move(output_optional).value())); - } else { - wrapped.push_back(EncodableValue()); - } - reply(EncodableValue(std::move(wrapped))); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); + channel.SetMessageHandler([api]( + const EncodableValue& message, + const flutter::MessageReply& + reply) { + try { + const auto& args = std::get(message); + const auto& encodable_everything_arg = args.at(0); + const auto* everything_arg = + encodable_everything_arg.IsNull() + ? nullptr + : &(std::any_cast( + std::get( + encodable_everything_arg))); + ErrorOr> output = + api->EchoAllNullableTypesWithoutRecursion(everything_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back( + CustomEncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); } else { channel.SetMessageHandler(nullptr); } @@ -2581,15 +2830,13 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, try { const auto& args = std::get(message); const auto& encodable_an_enum_arg = args.at(0); - const int64_t an_enum_arg_value = - encodable_an_enum_arg.IsNull() - ? 0 - : encodable_an_enum_arg.LongValue(); - const auto an_enum_arg = - encodable_an_enum_arg.IsNull() - ? std::nullopt - : std::make_optional( - static_cast(an_enum_arg_value)); + AnEnum an_enum_arg_value; + const AnEnum* an_enum_arg = nullptr; + if (!encodable_an_enum_arg.IsNull()) { + an_enum_arg_value = std::any_cast( + std::get(encodable_an_enum_arg)); + an_enum_arg = &an_enum_arg_value; + } ErrorOr> output = api->EchoNullableEnum( an_enum_arg ? &(*an_enum_arg) : nullptr); if (output.has_error()) { @@ -2600,7 +2847,7 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, auto output_optional = std::move(output).TakeValue(); if (output_optional) { wrapped.push_back( - EncodableValue((int)std::move(output_optional).value())); + CustomEncodableValue(std::move(output_optional).value())); } else { wrapped.push_back(EncodableValue()); } @@ -2961,15 +3208,15 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - if (encodable_a_list_arg.IsNull()) { - reply(WrapError("a_list_arg unexpectedly null.")); + const auto& encodable_list_arg = args.at(0); + if (encodable_list_arg.IsNull()) { + reply(WrapError("list_arg unexpectedly null.")); return; } - const auto& a_list_arg = - std::get(encodable_a_list_arg); + const auto& list_arg = + std::get(encodable_list_arg); api->EchoAsyncList( - a_list_arg, [reply](ErrorOr&& output) { + list_arg, [reply](ErrorOr&& output) { if (output.has_error()) { reply(WrapError(output.error())); return; @@ -3042,8 +3289,8 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, reply(WrapError("an_enum_arg unexpectedly null.")); return; } - const AnEnum& an_enum_arg = - (AnEnum)encodable_an_enum_arg.LongValue(); + const auto& an_enum_arg = std::any_cast( + std::get(encodable_an_enum_arg)); api->EchoAsyncEnum( an_enum_arg, [reply](ErrorOr&& output) { if (output.has_error()) { @@ -3052,7 +3299,7 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } EncodableList wrapped; wrapped.push_back( - EncodableValue((int)std::move(output).TakeValue())); + CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); }); } catch (const std::exception& exception) { @@ -3217,9 +3464,11 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& args = std::get(message); const auto& encodable_everything_arg = args.at(0); const auto* everything_arg = - &(std::any_cast( - std::get( - encodable_everything_arg))); + encodable_everything_arg.IsNull() + ? nullptr + : &(std::any_cast( + std::get( + encodable_everything_arg))); api->EchoAsyncNullableAllNullableTypes( everything_arg, [reply](ErrorOr>&& output) { @@ -3253,39 +3502,41 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler( - [api](const EncodableValue& message, - const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_everything_arg = args.at(0); - const auto* everything_arg = - &(std::any_cast( - std::get( - encodable_everything_arg))); - api->EchoAsyncNullableAllNullableTypesWithoutRecursion( - everything_arg, - [reply]( - ErrorOr>&& + channel.SetMessageHandler([api]( + const EncodableValue& message, + const flutter::MessageReply& + reply) { + try { + const auto& args = std::get(message); + const auto& encodable_everything_arg = args.at(0); + const auto* everything_arg = + encodable_everything_arg.IsNull() + ? nullptr + : &(std::any_cast( + std::get( + encodable_everything_arg))); + api->EchoAsyncNullableAllNullableTypesWithoutRecursion( + everything_arg, + [reply](ErrorOr>&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - auto output_optional = std::move(output).TakeValue(); - if (output_optional) { - wrapped.push_back(CustomEncodableValue( - std::move(output_optional).value())); - } else { - wrapped.push_back(EncodableValue()); - } - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back( + CustomEncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); } else { channel.SetMessageHandler(nullptr); } @@ -3551,11 +3802,11 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - const auto* a_list_arg = - std::get_if(&encodable_a_list_arg); + const auto& encodable_list_arg = args.at(0); + const auto* list_arg = + std::get_if(&encodable_list_arg); api->EchoAsyncNullableList( - a_list_arg, + list_arg, [reply](ErrorOr>&& output) { if (output.has_error()) { reply(WrapError(output.error())); @@ -3634,15 +3885,13 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, try { const auto& args = std::get(message); const auto& encodable_an_enum_arg = args.at(0); - const int64_t an_enum_arg_value = - encodable_an_enum_arg.IsNull() - ? 0 - : encodable_an_enum_arg.LongValue(); - const auto an_enum_arg = - encodable_an_enum_arg.IsNull() - ? std::nullopt - : std::make_optional( - static_cast(an_enum_arg_value)); + AnEnum an_enum_arg_value; + const AnEnum* an_enum_arg = nullptr; + if (!encodable_an_enum_arg.IsNull()) { + an_enum_arg_value = std::any_cast( + std::get(encodable_an_enum_arg)); + an_enum_arg = &an_enum_arg_value; + } api->EchoAsyncNullableEnum( an_enum_arg ? &(*an_enum_arg) : nullptr, [reply](ErrorOr>&& output) { @@ -3653,8 +3902,8 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, EncodableList wrapped; auto output_optional = std::move(output).TakeValue(); if (output_optional) { - wrapped.push_back(EncodableValue( - (int)std::move(output_optional).value())); + wrapped.push_back(CustomEncodableValue( + std::move(output_optional).value())); } else { wrapped.push_back(EncodableValue()); } @@ -3817,9 +4066,11 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& args = std::get(message); const auto& encodable_everything_arg = args.at(0); const auto* everything_arg = - &(std::any_cast( - std::get( - encodable_everything_arg))); + encodable_everything_arg.IsNull() + ? nullptr + : &(std::any_cast( + std::get( + encodable_everything_arg))); api->CallFlutterEchoAllNullableTypes( everything_arg, [reply](ErrorOr>&& output) { @@ -3902,39 +4153,41 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler( - [api](const EncodableValue& message, - const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_everything_arg = args.at(0); - const auto* everything_arg = - &(std::any_cast( - std::get( - encodable_everything_arg))); - api->CallFlutterEchoAllNullableTypesWithoutRecursion( - everything_arg, - [reply]( - ErrorOr>&& + channel.SetMessageHandler([api]( + const EncodableValue& message, + const flutter::MessageReply& + reply) { + try { + const auto& args = std::get(message); + const auto& encodable_everything_arg = args.at(0); + const auto* everything_arg = + encodable_everything_arg.IsNull() + ? nullptr + : &(std::any_cast( + std::get( + encodable_everything_arg))); + api->CallFlutterEchoAllNullableTypesWithoutRecursion( + everything_arg, + [reply](ErrorOr>&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - auto output_optional = std::move(output).TakeValue(); - if (output_optional) { - wrapped.push_back(CustomEncodableValue( - std::move(output_optional).value())); - } else { - wrapped.push_back(EncodableValue()); - } - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back( + CustomEncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); } else { channel.SetMessageHandler(nullptr); } @@ -4153,15 +4406,15 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - if (encodable_a_list_arg.IsNull()) { - reply(WrapError("a_list_arg unexpectedly null.")); + const auto& encodable_list_arg = args.at(0); + if (encodable_list_arg.IsNull()) { + reply(WrapError("list_arg unexpectedly null.")); return; } - const auto& a_list_arg = - std::get>(encodable_a_list_arg); + const auto& list_arg = + std::get>(encodable_list_arg); api->CallFlutterEchoUint8List( - a_list_arg, [reply](ErrorOr>&& output) { + list_arg, [reply](ErrorOr>&& output) { if (output.has_error()) { reply(WrapError(output.error())); return; @@ -4191,15 +4444,15 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - if (encodable_a_list_arg.IsNull()) { - reply(WrapError("a_list_arg unexpectedly null.")); + const auto& encodable_list_arg = args.at(0); + if (encodable_list_arg.IsNull()) { + reply(WrapError("list_arg unexpectedly null.")); return; } - const auto& a_list_arg = - std::get(encodable_a_list_arg); + const auto& list_arg = + std::get(encodable_list_arg); api->CallFlutterEchoList( - a_list_arg, [reply](ErrorOr&& output) { + list_arg, [reply](ErrorOr&& output) { if (output.has_error()) { reply(WrapError(output.error())); return; @@ -4272,8 +4525,8 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, reply(WrapError("an_enum_arg unexpectedly null.")); return; } - const AnEnum& an_enum_arg = - (AnEnum)encodable_an_enum_arg.LongValue(); + const auto& an_enum_arg = std::any_cast( + std::get(encodable_an_enum_arg)); api->CallFlutterEchoEnum( an_enum_arg, [reply](ErrorOr&& output) { if (output.has_error()) { @@ -4282,7 +4535,7 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, } EncodableList wrapped; wrapped.push_back( - EncodableValue((int)std::move(output).TakeValue())); + CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); }); } catch (const std::exception& exception) { @@ -4472,11 +4725,11 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - const auto* a_list_arg = - std::get_if>(&encodable_a_list_arg); + const auto& encodable_list_arg = args.at(0); + const auto* list_arg = + std::get_if>(&encodable_list_arg); api->CallFlutterEchoNullableUint8List( - a_list_arg, + list_arg, [reply]( ErrorOr>>&& output) { if (output.has_error()) { @@ -4514,11 +4767,11 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, const flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_a_list_arg = args.at(0); - const auto* a_list_arg = - std::get_if(&encodable_a_list_arg); + const auto& encodable_list_arg = args.at(0); + const auto* list_arg = + std::get_if(&encodable_list_arg); api->CallFlutterEchoNullableList( - a_list_arg, + list_arg, [reply](ErrorOr>&& output) { if (output.has_error()) { reply(WrapError(output.error())); @@ -4597,15 +4850,13 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, try { const auto& args = std::get(message); const auto& encodable_an_enum_arg = args.at(0); - const int64_t an_enum_arg_value = - encodable_an_enum_arg.IsNull() - ? 0 - : encodable_an_enum_arg.LongValue(); - const auto an_enum_arg = - encodable_an_enum_arg.IsNull() - ? std::nullopt - : std::make_optional( - static_cast(an_enum_arg_value)); + AnEnum an_enum_arg_value; + const AnEnum* an_enum_arg = nullptr; + if (!encodable_an_enum_arg.IsNull()) { + an_enum_arg_value = std::any_cast( + std::get(encodable_an_enum_arg)); + an_enum_arg = &an_enum_arg_value; + } api->CallFlutterEchoNullableEnum( an_enum_arg ? &(*an_enum_arg) : nullptr, [reply](ErrorOr>&& output) { @@ -4616,8 +4867,8 @@ void HostIntegrationCoreApi::SetUp(flutter::BinaryMessenger* binary_messenger, EncodableList wrapped; auto output_optional = std::move(output).TakeValue(); if (output_optional) { - wrapped.push_back(EncodableValue( - (int)std::move(output_optional).value())); + wrapped.push_back(CustomEncodableValue( + std::move(output_optional).value())); } else { wrapped.push_back(EncodableValue()); } @@ -4685,79 +4936,6 @@ EncodableValue HostIntegrationCoreApi::WrapError(const FlutterError& error) { error.details()}); } -FlutterIntegrationCoreApiCodecSerializer:: - FlutterIntegrationCoreApiCodecSerializer() {} - -EncodableValue FlutterIntegrationCoreApiCodecSerializer::ReadValueOfType( - uint8_t type, flutter::ByteStreamReader* stream) const { - switch (type) { - case 128: - return CustomEncodableValue(AllClassesWrapper::FromEncodableList( - std::get(ReadValue(stream)))); - case 129: - return CustomEncodableValue(AllNullableTypes::FromEncodableList( - std::get(ReadValue(stream)))); - case 130: - return CustomEncodableValue( - AllNullableTypesWithoutRecursion::FromEncodableList( - std::get(ReadValue(stream)))); - case 131: - return CustomEncodableValue(AllTypes::FromEncodableList( - std::get(ReadValue(stream)))); - case 132: - return CustomEncodableValue(TestMessage::FromEncodableList( - std::get(ReadValue(stream)))); - default: - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); - } -} - -void FlutterIntegrationCoreApiCodecSerializer::WriteValue( - const EncodableValue& value, flutter::ByteStreamWriter* stream) const { - if (const CustomEncodableValue* custom_value = - std::get_if(&value)) { - if (custom_value->type() == typeid(AllClassesWrapper)) { - stream->WriteByte(128); - WriteValue(EncodableValue(std::any_cast(*custom_value) - .ToEncodableList()), - stream); - return; - } - if (custom_value->type() == typeid(AllNullableTypes)) { - stream->WriteByte(129); - WriteValue( - EncodableValue( - std::any_cast(*custom_value).ToEncodableList()), - stream); - return; - } - if (custom_value->type() == typeid(AllNullableTypesWithoutRecursion)) { - stream->WriteByte(130); - WriteValue(EncodableValue(std::any_cast( - *custom_value) - .ToEncodableList()), - stream); - return; - } - if (custom_value->type() == typeid(AllTypes)) { - stream->WriteByte(131); - WriteValue(EncodableValue( - std::any_cast(*custom_value).ToEncodableList()), - stream); - return; - } - if (custom_value->type() == typeid(TestMessage)) { - stream->WriteByte(132); - WriteValue( - EncodableValue( - std::any_cast(*custom_value).ToEncodableList()), - stream); - return; - } - } - flutter::StandardCodecSerializer::WriteValue(value, stream); -} - // Generated class from Pigeon that represents Flutter messages that can be // called from C++. FlutterIntegrationCoreApi::FlutterIntegrationCoreApi( @@ -4774,7 +4952,7 @@ FlutterIntegrationCoreApi::FlutterIntegrationCoreApi( const flutter::StandardMessageCodec& FlutterIntegrationCoreApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &FlutterIntegrationCoreApiCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } void FlutterIntegrationCoreApi::Noop( @@ -4943,8 +5121,12 @@ void FlutterIntegrationCoreApi::EchoAllNullableTypes( std::get(list_return_value->at(1)), list_return_value->at(2))); } else { - const auto* return_value = &(std::any_cast( - std::get(list_return_value->at(0)))); + const auto* return_value = + list_return_value->at(0).IsNull() + ? nullptr + : &(std::any_cast( + std::get( + list_return_value->at(0)))); on_success(return_value); } } else { @@ -5026,8 +5208,11 @@ void FlutterIntegrationCoreApi::EchoAllNullableTypesWithoutRecursion( list_return_value->at(2))); } else { const auto* return_value = - &(std::any_cast( - std::get(list_return_value->at(0)))); + list_return_value->at(0).IsNull() + ? nullptr + : &(std::any_cast( + std::get( + list_return_value->at(0)))); on_success(return_value); } } else { @@ -5228,7 +5413,7 @@ void FlutterIntegrationCoreApi::EchoString( } void FlutterIntegrationCoreApi::EchoUint8List( - const std::vector& a_list_arg, + const std::vector& list_arg, std::function&)>&& on_success, std::function&& on_error) { const std::string channel_name = @@ -5237,7 +5422,7 @@ void FlutterIntegrationCoreApi::EchoUint8List( message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - EncodableValue(a_list_arg), + EncodableValue(list_arg), }); channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), @@ -5266,7 +5451,7 @@ void FlutterIntegrationCoreApi::EchoUint8List( } void FlutterIntegrationCoreApi::EchoList( - const EncodableList& a_list_arg, + const EncodableList& list_arg, std::function&& on_success, std::function&& on_error) { const std::string channel_name = @@ -5275,7 +5460,7 @@ void FlutterIntegrationCoreApi::EchoList( message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - EncodableValue(a_list_arg), + EncodableValue(list_arg), }); channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), @@ -5350,7 +5535,7 @@ void FlutterIntegrationCoreApi::EchoEnum( message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - EncodableValue((int)an_enum_arg), + CustomEncodableValue(an_enum_arg), }); channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), @@ -5368,8 +5553,8 @@ void FlutterIntegrationCoreApi::EchoEnum( std::get(list_return_value->at(1)), list_return_value->at(2))); } else { - const AnEnum& return_value = - (AnEnum)list_return_value->at(0).LongValue(); + const auto& return_value = std::any_cast( + std::get(list_return_value->at(0))); on_success(return_value); } } else { @@ -5531,7 +5716,7 @@ void FlutterIntegrationCoreApi::EchoNullableString( } void FlutterIntegrationCoreApi::EchoNullableUint8List( - const std::vector* a_list_arg, + const std::vector* list_arg, std::function*)>&& on_success, std::function&& on_error) { const std::string channel_name = @@ -5540,7 +5725,7 @@ void FlutterIntegrationCoreApi::EchoNullableUint8List( message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - a_list_arg ? EncodableValue(*a_list_arg) : EncodableValue(), + list_arg ? EncodableValue(*list_arg) : EncodableValue(), }); channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), @@ -5569,7 +5754,7 @@ void FlutterIntegrationCoreApi::EchoNullableUint8List( } void FlutterIntegrationCoreApi::EchoNullableList( - const EncodableList* a_list_arg, + const EncodableList* list_arg, std::function&& on_success, std::function&& on_error) { const std::string channel_name = @@ -5578,7 +5763,7 @@ void FlutterIntegrationCoreApi::EchoNullableList( message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - a_list_arg ? EncodableValue(*a_list_arg) : EncodableValue(), + list_arg ? EncodableValue(*list_arg) : EncodableValue(), }); channel.Send( encoded_api_arguments, [channel_name, on_success = std::move(on_success), @@ -5653,37 +5838,37 @@ void FlutterIntegrationCoreApi::EchoNullableEnum( message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - an_enum_arg ? EncodableValue((int)(*an_enum_arg)) : EncodableValue(), - }); - channel.Send(encoded_api_arguments, [channel_name, - on_success = std::move(on_success), - on_error = std::move(on_error)]( - const uint8_t* reply, - size_t reply_size) { - std::unique_ptr response = - GetCodec().DecodeMessage(reply, reply_size); - const auto& encodable_return_value = *response; - const auto* list_return_value = - std::get_if(&encodable_return_value); - if (list_return_value) { - if (list_return_value->size() > 1) { - on_error(FlutterError(std::get(list_return_value->at(0)), - std::get(list_return_value->at(1)), - list_return_value->at(2))); - } else { - const int64_t return_value_value = - list_return_value->at(0).IsNull() - ? 0 - : list_return_value->at(0).LongValue(); - const AnEnum enum_return_value = (AnEnum)return_value_value; - const auto* return_value = - list_return_value->at(0).IsNull() ? nullptr : &enum_return_value; - on_success(return_value); - } - } else { - on_error(CreateConnectionError(channel_name)); - } + an_enum_arg ? CustomEncodableValue(*an_enum_arg) : EncodableValue(), }); + channel.Send( + encoded_api_arguments, [channel_name, on_success = std::move(on_success), + on_error = std::move(on_error)]( + const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = + GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = + std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error( + FlutterError(std::get(list_return_value->at(0)), + std::get(list_return_value->at(1)), + list_return_value->at(2))); + } else { + AnEnum return_value_value; + const AnEnum* return_value = nullptr; + if (!list_return_value->at(0).IsNull()) { + return_value_value = std::any_cast( + std::get(list_return_value->at(0))); + return_value = &return_value_value; + } + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); } void FlutterIntegrationCoreApi::NoopAsync( @@ -5760,7 +5945,7 @@ void FlutterIntegrationCoreApi::EchoAsyncString( /// The codec used by HostTrivialApi. const flutter::StandardMessageCodec& HostTrivialApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &flutter::StandardCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } // Sets up an instance of `HostTrivialApi` to handle messages through the @@ -5821,7 +6006,7 @@ EncodableValue HostTrivialApi::WrapError(const FlutterError& error) { /// The codec used by HostSmallApi. const flutter::StandardMessageCodec& HostSmallApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &flutter::StandardCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } // Sets up an instance of `HostSmallApi` to handle messages through the @@ -5917,35 +6102,6 @@ EncodableValue HostSmallApi::WrapError(const FlutterError& error) { error.details()}); } -FlutterSmallApiCodecSerializer::FlutterSmallApiCodecSerializer() {} - -EncodableValue FlutterSmallApiCodecSerializer::ReadValueOfType( - uint8_t type, flutter::ByteStreamReader* stream) const { - switch (type) { - case 128: - return CustomEncodableValue(TestMessage::FromEncodableList( - std::get(ReadValue(stream)))); - default: - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); - } -} - -void FlutterSmallApiCodecSerializer::WriteValue( - const EncodableValue& value, flutter::ByteStreamWriter* stream) const { - if (const CustomEncodableValue* custom_value = - std::get_if(&value)) { - if (custom_value->type() == typeid(TestMessage)) { - stream->WriteByte(128); - WriteValue( - EncodableValue( - std::any_cast(*custom_value).ToEncodableList()), - stream); - return; - } - } - flutter::StandardCodecSerializer::WriteValue(value, stream); -} - // Generated class from Pigeon that represents Flutter messages that can be // called from C++. FlutterSmallApi::FlutterSmallApi(flutter::BinaryMessenger* binary_messenger) @@ -5960,7 +6116,7 @@ FlutterSmallApi::FlutterSmallApi(flutter::BinaryMessenger* binary_messenger, const flutter::StandardMessageCodec& FlutterSmallApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &FlutterSmallApiCodecSerializer::GetInstance()); + &PigeonCodecSerializer::GetInstance()); } void FlutterSmallApi::EchoWrappedList( diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h index 15a98bf6bba..ceed1b08727 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h +++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h @@ -84,10 +84,14 @@ class AllTypes { const std::vector& a4_byte_array, const std::vector& a8_byte_array, const std::vector& a_float_array, - const flutter::EncodableList& a_list, - const flutter::EncodableMap& a_map, const AnEnum& an_enum, - const std::string& a_string, - const flutter::EncodableValue& an_object); + const AnEnum& an_enum, const std::string& a_string, + const flutter::EncodableValue& an_object, + const flutter::EncodableList& list, + const flutter::EncodableList& string_list, + const flutter::EncodableList& int_list, + const flutter::EncodableList& double_list, + const flutter::EncodableList& bool_list, + const flutter::EncodableMap& map); bool a_bool() const; void set_a_bool(bool value_arg); @@ -113,12 +117,6 @@ class AllTypes { const std::vector& a_float_array() const; void set_a_float_array(const std::vector& value_arg); - const flutter::EncodableList& a_list() const; - void set_a_list(const flutter::EncodableList& value_arg); - - const flutter::EncodableMap& a_map() const; - void set_a_map(const flutter::EncodableMap& value_arg); - const AnEnum& an_enum() const; void set_an_enum(const AnEnum& value_arg); @@ -128,20 +126,34 @@ class AllTypes { const flutter::EncodableValue& an_object() const; void set_an_object(const flutter::EncodableValue& value_arg); + const flutter::EncodableList& list() const; + void set_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList& string_list() const; + void set_string_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList& int_list() const; + void set_int_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList& double_list() const; + void set_double_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList& bool_list() const; + void set_bool_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableMap& map() const; + void set_map(const flutter::EncodableMap& value_arg); + private: static AllTypes FromEncodableList(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class AllClassesWrapper; friend class HostIntegrationCoreApi; - friend class HostIntegrationCoreApiCodecSerializer; friend class FlutterIntegrationCoreApi; - friend class FlutterIntegrationCoreApiCodecSerializer; friend class HostTrivialApi; - friend class HostTrivialApiCodecSerializer; friend class HostSmallApi; - friend class HostSmallApiCodecSerializer; friend class FlutterSmallApi; - friend class FlutterSmallApiCodecSerializer; + friend class PigeonCodecSerializer; friend class CoreTestsTest; bool a_bool_; int64_t an_int_; @@ -151,11 +163,15 @@ class AllTypes { std::vector a4_byte_array_; std::vector a8_byte_array_; std::vector a_float_array_; - flutter::EncodableList a_list_; - flutter::EncodableMap a_map_; AnEnum an_enum_; std::string a_string_; flutter::EncodableValue an_object_; + flutter::EncodableList list_; + flutter::EncodableList string_list_; + flutter::EncodableList int_list_; + flutter::EncodableList double_list_; + flutter::EncodableList bool_list_; + flutter::EncodableMap map_; }; // A class containing all supported nullable types. @@ -174,14 +190,19 @@ class AllNullableTypes { const std::vector* a_nullable4_byte_array, const std::vector* a_nullable8_byte_array, const std::vector* a_nullable_float_array, - const flutter::EncodableList* a_nullable_list, - const flutter::EncodableMap* a_nullable_map, const flutter::EncodableList* nullable_nested_list, const flutter::EncodableMap* nullable_map_with_annotations, const flutter::EncodableMap* nullable_map_with_object, const AnEnum* a_nullable_enum, const std::string* a_nullable_string, const flutter::EncodableValue* a_nullable_object, - const AllNullableTypes* all_nullable_types); + const AllNullableTypes* all_nullable_types, + const flutter::EncodableList* list, + const flutter::EncodableList* string_list, + const flutter::EncodableList* int_list, + const flutter::EncodableList* double_list, + const flutter::EncodableList* bool_list, + const flutter::EncodableList* nested_class_list, + const flutter::EncodableMap* map); ~AllNullableTypes() = default; AllNullableTypes(const AllNullableTypes& other); @@ -220,14 +241,6 @@ class AllNullableTypes { void set_a_nullable_float_array(const std::vector* value_arg); void set_a_nullable_float_array(const std::vector& value_arg); - const flutter::EncodableList* a_nullable_list() const; - void set_a_nullable_list(const flutter::EncodableList* value_arg); - void set_a_nullable_list(const flutter::EncodableList& value_arg); - - const flutter::EncodableMap* a_nullable_map() const; - void set_a_nullable_map(const flutter::EncodableMap* value_arg); - void set_a_nullable_map(const flutter::EncodableMap& value_arg); - const flutter::EncodableList* nullable_nested_list() const; void set_nullable_nested_list(const flutter::EncodableList* value_arg); void set_nullable_nested_list(const flutter::EncodableList& value_arg); @@ -258,20 +271,44 @@ class AllNullableTypes { void set_all_nullable_types(const AllNullableTypes* value_arg); void set_all_nullable_types(const AllNullableTypes& value_arg); + const flutter::EncodableList* list() const; + void set_list(const flutter::EncodableList* value_arg); + void set_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* string_list() const; + void set_string_list(const flutter::EncodableList* value_arg); + void set_string_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* int_list() const; + void set_int_list(const flutter::EncodableList* value_arg); + void set_int_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* double_list() const; + void set_double_list(const flutter::EncodableList* value_arg); + void set_double_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* bool_list() const; + void set_bool_list(const flutter::EncodableList* value_arg); + void set_bool_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* nested_class_list() const; + void set_nested_class_list(const flutter::EncodableList* value_arg); + void set_nested_class_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableMap* map() const; + void set_map(const flutter::EncodableMap* value_arg); + void set_map(const flutter::EncodableMap& value_arg); + private: static AllNullableTypes FromEncodableList(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class AllClassesWrapper; friend class HostIntegrationCoreApi; - friend class HostIntegrationCoreApiCodecSerializer; friend class FlutterIntegrationCoreApi; - friend class FlutterIntegrationCoreApiCodecSerializer; friend class HostTrivialApi; - friend class HostTrivialApiCodecSerializer; friend class HostSmallApi; - friend class HostSmallApiCodecSerializer; friend class FlutterSmallApi; - friend class FlutterSmallApiCodecSerializer; + friend class PigeonCodecSerializer; friend class CoreTestsTest; std::optional a_nullable_bool_; std::optional a_nullable_int_; @@ -281,8 +318,6 @@ class AllNullableTypes { std::optional> a_nullable4_byte_array_; std::optional> a_nullable8_byte_array_; std::optional> a_nullable_float_array_; - std::optional a_nullable_list_; - std::optional a_nullable_map_; std::optional nullable_nested_list_; std::optional nullable_map_with_annotations_; std::optional nullable_map_with_object_; @@ -290,6 +325,13 @@ class AllNullableTypes { std::optional a_nullable_string_; std::optional a_nullable_object_; std::unique_ptr all_nullable_types_; + std::optional list_; + std::optional string_list_; + std::optional int_list_; + std::optional double_list_; + std::optional bool_list_; + std::optional nested_class_list_; + std::optional map_; }; // The primary purpose for this class is to ensure coverage of Swift structs @@ -310,13 +352,17 @@ class AllNullableTypesWithoutRecursion { const std::vector* a_nullable4_byte_array, const std::vector* a_nullable8_byte_array, const std::vector* a_nullable_float_array, - const flutter::EncodableList* a_nullable_list, - const flutter::EncodableMap* a_nullable_map, const flutter::EncodableList* nullable_nested_list, const flutter::EncodableMap* nullable_map_with_annotations, const flutter::EncodableMap* nullable_map_with_object, const AnEnum* a_nullable_enum, const std::string* a_nullable_string, - const flutter::EncodableValue* a_nullable_object); + const flutter::EncodableValue* a_nullable_object, + const flutter::EncodableList* list, + const flutter::EncodableList* string_list, + const flutter::EncodableList* int_list, + const flutter::EncodableList* double_list, + const flutter::EncodableList* bool_list, + const flutter::EncodableMap* map); const bool* a_nullable_bool() const; void set_a_nullable_bool(const bool* value_arg); @@ -350,14 +396,6 @@ class AllNullableTypesWithoutRecursion { void set_a_nullable_float_array(const std::vector* value_arg); void set_a_nullable_float_array(const std::vector& value_arg); - const flutter::EncodableList* a_nullable_list() const; - void set_a_nullable_list(const flutter::EncodableList* value_arg); - void set_a_nullable_list(const flutter::EncodableList& value_arg); - - const flutter::EncodableMap* a_nullable_map() const; - void set_a_nullable_map(const flutter::EncodableMap* value_arg); - void set_a_nullable_map(const flutter::EncodableMap& value_arg); - const flutter::EncodableList* nullable_nested_list() const; void set_nullable_nested_list(const flutter::EncodableList* value_arg); void set_nullable_nested_list(const flutter::EncodableList& value_arg); @@ -384,21 +422,41 @@ class AllNullableTypesWithoutRecursion { void set_a_nullable_object(const flutter::EncodableValue* value_arg); void set_a_nullable_object(const flutter::EncodableValue& value_arg); + const flutter::EncodableList* list() const; + void set_list(const flutter::EncodableList* value_arg); + void set_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* string_list() const; + void set_string_list(const flutter::EncodableList* value_arg); + void set_string_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* int_list() const; + void set_int_list(const flutter::EncodableList* value_arg); + void set_int_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* double_list() const; + void set_double_list(const flutter::EncodableList* value_arg); + void set_double_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableList* bool_list() const; + void set_bool_list(const flutter::EncodableList* value_arg); + void set_bool_list(const flutter::EncodableList& value_arg); + + const flutter::EncodableMap* map() const; + void set_map(const flutter::EncodableMap* value_arg); + void set_map(const flutter::EncodableMap& value_arg); + private: static AllNullableTypesWithoutRecursion FromEncodableList( const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class AllClassesWrapper; friend class HostIntegrationCoreApi; - friend class HostIntegrationCoreApiCodecSerializer; friend class FlutterIntegrationCoreApi; - friend class FlutterIntegrationCoreApiCodecSerializer; friend class HostTrivialApi; - friend class HostTrivialApiCodecSerializer; friend class HostSmallApi; - friend class HostSmallApiCodecSerializer; friend class FlutterSmallApi; - friend class FlutterSmallApiCodecSerializer; + friend class PigeonCodecSerializer; friend class CoreTestsTest; std::optional a_nullable_bool_; std::optional a_nullable_int_; @@ -408,14 +466,18 @@ class AllNullableTypesWithoutRecursion { std::optional> a_nullable4_byte_array_; std::optional> a_nullable8_byte_array_; std::optional> a_nullable_float_array_; - std::optional a_nullable_list_; - std::optional a_nullable_map_; std::optional nullable_nested_list_; std::optional nullable_map_with_annotations_; std::optional nullable_map_with_object_; std::optional a_nullable_enum_; std::optional a_nullable_string_; std::optional a_nullable_object_; + std::optional list_; + std::optional string_list_; + std::optional int_list_; + std::optional double_list_; + std::optional bool_list_; + std::optional map_; }; // A class for testing nested class handling. @@ -460,15 +522,11 @@ class AllClassesWrapper { const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class HostIntegrationCoreApi; - friend class HostIntegrationCoreApiCodecSerializer; friend class FlutterIntegrationCoreApi; - friend class FlutterIntegrationCoreApiCodecSerializer; friend class HostTrivialApi; - friend class HostTrivialApiCodecSerializer; friend class HostSmallApi; - friend class HostSmallApiCodecSerializer; friend class FlutterSmallApi; - friend class FlutterSmallApiCodecSerializer; + friend class PigeonCodecSerializer; friend class CoreTestsTest; std::unique_ptr all_nullable_types_; std::unique_ptr @@ -495,25 +553,20 @@ class TestMessage { static TestMessage FromEncodableList(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class HostIntegrationCoreApi; - friend class HostIntegrationCoreApiCodecSerializer; friend class FlutterIntegrationCoreApi; - friend class FlutterIntegrationCoreApiCodecSerializer; friend class HostTrivialApi; - friend class HostTrivialApiCodecSerializer; friend class HostSmallApi; - friend class HostSmallApiCodecSerializer; friend class FlutterSmallApi; - friend class FlutterSmallApiCodecSerializer; + friend class PigeonCodecSerializer; friend class CoreTestsTest; std::optional test_list_; }; -class HostIntegrationCoreApiCodecSerializer - : public flutter::StandardCodecSerializer { +class PigeonCodecSerializer : public flutter::StandardCodecSerializer { public: - HostIntegrationCoreApiCodecSerializer(); - inline static HostIntegrationCoreApiCodecSerializer& GetInstance() { - static HostIntegrationCoreApiCodecSerializer sInstance; + PigeonCodecSerializer(); + inline static PigeonCodecSerializer& GetInstance() { + static PigeonCodecSerializer sInstance; return sInstance; } @@ -563,7 +616,7 @@ class HostIntegrationCoreApi { const flutter::EncodableValue& an_object) = 0; // Returns the passed list, to test serialization and deserialization. virtual ErrorOr EchoList( - const flutter::EncodableList& a_list) = 0; + const flutter::EncodableList& list) = 0; // Returns the passed map, to test serialization and deserialization. virtual ErrorOr EchoMap( const flutter::EncodableMap& a_map) = 0; @@ -664,7 +717,7 @@ class HostIntegrationCoreApi { // Returns the passed list, to test asynchronous serialization and // deserialization. virtual void EchoAsyncList( - const flutter::EncodableList& a_list, + const flutter::EncodableList& list, std::function reply)> result) = 0; // Returns the passed map, to test asynchronous serialization and // deserialization. @@ -732,7 +785,7 @@ class HostIntegrationCoreApi { // Returns the passed list, to test asynchronous serialization and // deserialization. virtual void EchoAsyncNullableList( - const flutter::EncodableList* a_list, + const flutter::EncodableList* list, std::function> reply)> result) = 0; // Returns the passed map, to test asynchronous serialization and @@ -784,10 +837,10 @@ class HostIntegrationCoreApi { const std::string& a_string, std::function reply)> result) = 0; virtual void CallFlutterEchoUint8List( - const std::vector& a_list, + const std::vector& list, std::function> reply)> result) = 0; virtual void CallFlutterEchoList( - const flutter::EncodableList& a_list, + const flutter::EncodableList& list, std::function reply)> result) = 0; virtual void CallFlutterEchoMap( const flutter::EncodableMap& a_map, @@ -809,11 +862,11 @@ class HostIntegrationCoreApi { std::function> reply)> result) = 0; virtual void CallFlutterEchoNullableUint8List( - const std::vector* a_list, + const std::vector* list, std::function>> reply)> result) = 0; virtual void CallFlutterEchoNullableList( - const flutter::EncodableList* a_list, + const flutter::EncodableList* list, std::function> reply)> result) = 0; virtual void CallFlutterEchoNullableMap( @@ -842,23 +895,6 @@ class HostIntegrationCoreApi { protected: HostIntegrationCoreApi() = default; }; -class FlutterIntegrationCoreApiCodecSerializer - : public flutter::StandardCodecSerializer { - public: - FlutterIntegrationCoreApiCodecSerializer(); - inline static FlutterIntegrationCoreApiCodecSerializer& GetInstance() { - static FlutterIntegrationCoreApiCodecSerializer sInstance; - return sInstance; - } - - void WriteValue(const flutter::EncodableValue& value, - flutter::ByteStreamWriter* stream) const override; - - protected: - flutter::EncodableValue ReadValueOfType( - uint8_t type, flutter::ByteStreamReader* stream) const override; -}; - // The core interface that the Dart platform_test code implements for host // integration tests to call into. // @@ -926,11 +962,11 @@ class FlutterIntegrationCoreApi { std::function&& on_error); // Returns the passed byte list, to test serialization and deserialization. void EchoUint8List( - const std::vector& a_list, + const std::vector& list, std::function&)>&& on_success, std::function&& on_error); // Returns the passed list, to test serialization and deserialization. - void EchoList(const flutter::EncodableList& a_list, + void EchoList(const flutter::EncodableList& list, std::function&& on_success, std::function&& on_error); // Returns the passed map, to test serialization and deserialization. @@ -959,12 +995,12 @@ class FlutterIntegrationCoreApi { std::function&& on_error); // Returns the passed byte list, to test serialization and deserialization. void EchoNullableUint8List( - const std::vector* a_list, + const std::vector* list, std::function*)>&& on_success, std::function&& on_error); // Returns the passed list, to test serialization and deserialization. void EchoNullableList( - const flutter::EncodableList* a_list, + const flutter::EncodableList* list, std::function&& on_success, std::function&& on_error); // Returns the passed map, to test serialization and deserialization. @@ -1045,22 +1081,6 @@ class HostSmallApi { protected: HostSmallApi() = default; }; -class FlutterSmallApiCodecSerializer : public flutter::StandardCodecSerializer { - public: - FlutterSmallApiCodecSerializer(); - inline static FlutterSmallApiCodecSerializer& GetInstance() { - static FlutterSmallApiCodecSerializer sInstance; - return sInstance; - } - - void WriteValue(const flutter::EncodableValue& value, - flutter::ByteStreamWriter* stream) const override; - - protected: - flutter::EncodableValue ReadValueOfType( - uint8_t type, flutter::ByteStreamReader* stream) const override; -}; - // A simple API called in some unit tests. // // Generated class from Pigeon that represents Flutter messages that can be diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test/null_fields_test.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test/null_fields_test.cpp index fa21a9619ac..e6a366248f0 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test/null_fields_test.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/test/null_fields_test.cpp @@ -11,6 +11,7 @@ namespace null_fields_pigeontest { namespace { +using flutter::CustomEncodableValue; using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; @@ -113,19 +114,14 @@ TEST_F(NullFieldsTest, RequestFromListWithNulls) { } TEST_F(NullFieldsTest, ReplyFromListWithValues) { + NullFieldsSearchRequest request(1); + request.set_query("hello"); EncodableList list{ EncodableValue("result"), EncodableValue("error"), - EncodableValue(EncodableList{ - EncodableValue(1), - EncodableValue(2), - EncodableValue(3), - }), - EncodableValue(EncodableList{ - EncodableValue("hello"), - EncodableValue(1), - }), - EncodableValue(0), + EncodableValue(EncodableList({1, 2, 3})), + CustomEncodableValue(request), + CustomEncodableValue(NullFieldsSearchReplyType::success), }; NullFieldsSearchReply reply = ReplyFromList(list); @@ -194,10 +190,11 @@ TEST_F(NullFieldsTest, ReplyToMapWithValues) { EXPECT_EQ(indices[0].LongValue(), 1L); EXPECT_EQ(indices[1].LongValue(), 2L); EXPECT_EQ(indices[2].LongValue(), 3L); - const EncodableList& request_list = - *ExpectAndGetIndex(list, 3); - EXPECT_EQ(*ExpectAndGetIndex(request_list, 0), "hello"); - EXPECT_EQ(*ExpectAndGetIndex(request_list, 1), 1); + const NullFieldsSearchRequest& request_from_list = + std::any_cast( + *ExpectAndGetIndex(list, 3)); + EXPECT_EQ(*request_from_list.query(), "hello"); + EXPECT_EQ(request_from_list.identifier(), 1); } TEST_F(NullFieldsTest, ReplyToListWithNulls) { diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test/pigeon_test.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test/pigeon_test.cpp index 89118efc10d..427a62f06c6 100644 --- a/packages/pigeon/platform_tests/test_plugin/windows/test/pigeon_test.cpp +++ b/packages/pigeon/platform_tests/test_plugin/windows/test/pigeon_test.cpp @@ -137,7 +137,7 @@ TEST(PigeonTests, CallSearch) { Writer writer; flutter::EncodableList args; args.push_back(flutter::CustomEncodableValue(request)); - MessageApiCodecSerializer::GetInstance().WriteValue(args, &writer); + PigeonCodecSerializer::GetInstance().WriteValue(args, &writer); handler(writer.data_.data(), writer.data_.size(), reply); EXPECT_TRUE(did_call_reply); } diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index f368b97536a..411d3bdb581 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,10 +2,10 @@ name: pigeon description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. repository: https://github.com/flutter/packages/tree/main/packages/pigeon issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22 -version: 18.0.0 # This must match the version in lib/generator_tools.dart +version: 20.0.2 # This must match the version in lib/generator_tools.dart environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: analyzer: ">=5.13.0 <7.0.0" diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index c2fdcd68f68..80ffc070224 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -651,8 +651,7 @@ void main() { expect( code, contains( - 'nullable_nested_ ? EncodableValue(nullable_nested_->ToEncodableList()) ' - ': EncodableValue()')); + 'nullable_nested_ ? CustomEncodableValue(*nullable_nested_) : EncodableValue())')); // Serialization should use push_back, not initializer lists, to avoid // copies. @@ -805,13 +804,15 @@ void main() { 'non_nullable_nested_ = std::make_unique(value_arg);')); // Serialization uses the value directly. expect(code, contains('EncodableValue(non_nullable_bool_)')); - expect(code, contains('non_nullable_nested_->ToEncodableList()')); + expect(code, contains('CustomEncodableValue(*non_nullable_nested_)')); // Serialization should use push_back, not initializer lists, to avoid // copies. expect(code, contains('list.reserve(4)')); expect( - code, contains('list.push_back(EncodableValue(non_nullable_bool_))')); + code, + contains( + 'list.push_back(CustomEncodableValue(*non_nullable_nested_))')); } }); @@ -1193,7 +1194,7 @@ void main() { expect( code, contains( - 'const auto* an_object_arg = &(std::any_cast(std::get(encodable_an_object_arg)));')); + 'const auto* an_object_arg = encodable_an_object_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_an_object_arg)));')); // "Object" requires no extraction at all since it has to use // EncodableValue directly. expect( @@ -1637,13 +1638,13 @@ void main() { dartPackageName: DEFAULT_PACKAGE_NAME, ); final String code = sink.toString(); - // Standard types are wrapped an EncodableValues. + // Standard types are wrapped in EncodableValues. expect(code, contains('EncodableValue(a_bool_arg)')); expect(code, contains('EncodableValue(an_int_arg)')); expect(code, contains('EncodableValue(a_string_arg)')); expect(code, contains('EncodableValue(a_list_arg)')); expect(code, contains('EncodableValue(a_map_arg)')); - // Class types use ToEncodableList. + // Class types are wrapped in CustomEncodableValues. expect(code, contains('CustomEncodableValue(an_object_arg)')); } }); @@ -1813,50 +1814,7 @@ void main() { expect(code, contains('// ///')); }); - test("doesn't create codecs if no custom datatypes", () { - final Root root = Root( - apis: [ - AstFlutterApi( - name: 'Api', - methods: [ - Method( - name: 'method', - location: ApiLocation.flutter, - returnType: const TypeDeclaration.voidDeclaration(), - parameters: [ - Parameter( - name: 'field', - type: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - ), - ], - ) - ], - ) - ], - classes: [], - enums: [], - ); - final StringBuffer sink = StringBuffer(); - const CppGenerator generator = CppGenerator(); - final OutputFileOptions generatorOptions = - OutputFileOptions( - fileType: FileType.header, - languageOptions: const CppOptions(), - ); - generator.generate( - generatorOptions, - root, - sink, - dartPackageName: DEFAULT_PACKAGE_NAME, - ); - final String code = sink.toString(); - expect(code, isNot(contains(' : public flutter::StandardCodecSerializer'))); - }); - - test('creates custom codecs if custom datatypes present', () { + test('creates custom codecs', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( diff --git a/packages/pigeon/test/dart/proxy_api_test.dart b/packages/pigeon/test/dart/proxy_api_test.dart index fb24a203861..a8b3a5a9eb0 100644 --- a/packages/pigeon/test/dart/proxy_api_test.dart +++ b/packages/pigeon/test/dart/proxy_api_test.dart @@ -126,6 +126,56 @@ void main() { expect(code, contains(r'Api pigeon_copy(')); }); + test('InstanceManagerApi', () { + final Root root = Root(apis: [ + AstProxyApi( + name: 'Api', + constructors: [], + fields: [], + methods: [], + ) + ], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + const DartGenerator generator = DartGenerator(); + generator.generate( + const DartOptions(), + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + final String collapsedCode = _collapseNewlineAndIndentation(code); + + expect(code, contains(r'class _PigeonInstanceManagerApi')); + + expect( + code, + contains( + 'Future removeStrongReference(int identifier)', + ), + ); + expect( + code, + contains( + 'dev.flutter.pigeon.$DEFAULT_PACKAGE_NAME.PigeonInstanceManagerApi.removeStrongReference', + ), + ); + expect( + collapsedCode, + contains( + '(instanceManager ?? PigeonInstanceManager.instance) .remove(arg_identifier!);', + ), + ); + + expect(code, contains('Future clear()')); + expect( + code, + contains( + 'dev.flutter.pigeon.$DEFAULT_PACKAGE_NAME.PigeonInstanceManagerApi.clear', + ), + ); + }); + group('inheritance', () { test('extends', () { final AstProxyApi api2 = AstProxyApi( @@ -467,8 +517,8 @@ void main() { contains( r'__pigeon_channel.send([ ' r'__pigeon_instanceIdentifier, ' - r'validType, enumType.index, proxyApiType, ' - r'nullableValidType, nullableEnumType?.index, nullableProxyApiType ])', + r'validType, enumType, proxyApiType, ' + r'nullableValidType, nullableEnumType, nullableProxyApiType ])', ), ); }); @@ -577,8 +627,8 @@ void main() { contains( r'__pigeon_channel.send([ ' r'__pigeon_instanceIdentifier, ' - r'validType, enumType.index, proxyApiType, ' - r'nullableValidType, nullableEnumType?.index, nullableProxyApiType ])', + r'validType, enumType, proxyApiType, ' + r'nullableValidType, nullableEnumType, nullableProxyApiType ])', ), ); expect( @@ -797,8 +847,8 @@ void main() { collapsedCode, contains( r'await __pigeon_channel.send([ this, validType, ' - r'enumType.index, proxyApiType, nullableValidType, ' - r'nullableEnumType?.index, nullableProxyApiType ])', + r'enumType, proxyApiType, nullableValidType, ' + r'nullableEnumType, nullableProxyApiType ])', ), ); }); @@ -954,10 +1004,9 @@ void main() { contains(r'final int? arg_validType = (args[1] as int?);'), ); expect( - collapsedCode, + code, contains( - r'final AnEnum? arg_enumType = args[2] == null ? ' - r'null : AnEnum.values[args[2]! as int];', + r'final AnEnum? arg_enumType = (args[2] as AnEnum?);', ), ); expect( @@ -969,10 +1018,9 @@ void main() { contains(r'final int? arg_nullableValidType = (args[4] as int?);'), ); expect( - collapsedCode, + code, contains( - r'final AnEnum? arg_nullableEnumType = args[5] == null ? ' - r'null : AnEnum.values[args[5]! as int];', + r'final AnEnum? arg_nullableEnumType = (args[5] as AnEnum?);', ), ); expect( diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index bdb8ece598f..944c6e334ce 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -246,13 +246,13 @@ void main() { expect( code, contains( - 'nested?.encode(),', + 'nested,', ), ); expect( - code.replaceAll('\n', ' ').replaceAll(' ', ''), + code, contains( - 'nested: result[0] != null ? Input.decode(result[0]! as List) : null', + 'nested: result[0] as Input?', ), ); }); @@ -295,13 +295,13 @@ void main() { expect( code, contains( - 'nested.encode(),', + 'nested,', ), ); expect( - code.replaceAll('\n', ' ').replaceAll(' ', ''), + code, contains( - 'nested: Input.decode(result[0]! as List)', + 'nested: result[0]! as Input', ), ); }); @@ -532,8 +532,10 @@ void main() { dartPackageName: DEFAULT_PACKAGE_NAME, ); final String code = sink.toString(); - expect(code, contains('enum1?.index,')); - expect(code, contains('? Enum.values[result[0]! as int]')); + expect(code, contains('return value == null ? null : Enum.values[value];')); + expect(code, contains('writeValue(buffer, value.index);')); + expect(code, + contains('final EnumClass? arg_enumClass = (args[0] as EnumClass?);')); expect(code, contains('EnumClass doSomething(EnumClass enumClass);')); }); @@ -571,7 +573,7 @@ void main() { final String code = sink.toString(); expect(code, contains('enum Foo {')); expect(code, contains('Future bar(Foo? foo) async')); - expect(code, contains('__pigeon_channel.send([foo?.index])')); + expect(code, contains('__pigeon_channel.send([foo])')); }); test('flutter non-nullable enum argument with enum class', () { @@ -624,8 +626,9 @@ void main() { dartPackageName: DEFAULT_PACKAGE_NAME, ); final String code = sink.toString(); - expect(code, contains('enum1.index,')); - expect(code, contains('enum1: Enum.values[result[0]! as int]')); + expect(code, contains('writeValue(buffer, value.index)')); + expect(code, contains('return value == null ? null : Enum.values[value];')); + expect(code, contains('enum1: result[0]! as Enum,')); }); test('host void argument', () { @@ -1600,46 +1603,7 @@ name: foobar expect(code, contains('/// ///')); }); - test("doesn't create codecs if no custom datatypes", () { - final Root root = Root( - apis: [ - AstFlutterApi( - name: 'Api', - methods: [ - Method( - name: 'method', - location: ApiLocation.flutter, - returnType: const TypeDeclaration.voidDeclaration(), - parameters: [ - Parameter( - name: 'field', - type: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - ), - ], - ) - ], - ) - ], - classes: [], - enums: [], - ); - final StringBuffer sink = StringBuffer(); - const DartGenerator generator = DartGenerator(); - generator.generate( - const DartOptions(), - root, - sink, - dartPackageName: DEFAULT_PACKAGE_NAME, - ); - final String code = sink.toString(); - expect(code, isNot(contains('extends StandardMessageCodec'))); - expect(code, contains('StandardMessageCodec')); - }); - - test('creates custom codecs if custom datatypes present', () { + test('creates custom codecs', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( @@ -1740,10 +1704,10 @@ name: foobar ); final String testCode = sink.toString(); - expect( - testCode, - contains( - 'final Enum? arg_anEnum = args[0] == null ? null : Enum.values[args[0]! as int]')); + expect(testCode, contains('final Enum? arg_anEnum = (args[0] as Enum?);')); + expect(testCode, + contains('return value == null ? null : Enum.values[value];')); + expect(testCode, contains('writeValue(buffer, value.index);')); }); test('connection error contains channel name', () { diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index e68bb96239c..d7c49e37154 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -44,11 +44,6 @@ final Class emptyClass = Class(name: 'className', fields: [ ) ]); -final Enum emptyEnum = Enum( - name: 'enumName', - members: [EnumMember(name: 'enumMemberName')], -); - void main() { test('test merge maps', () { final Map source = { @@ -77,144 +72,26 @@ void main() { expect(_equalMaps(expected, mergeMaps(source, modification)), isTrue); }); - test('get codec classes from argument type arguments', () { - final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ - Method( - name: 'doSomething', - location: ApiLocation.flutter, - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'List', - isNullable: false, - typeArguments: [ - TypeDeclaration( - baseName: 'Input', - isNullable: true, - associatedClass: emptyClass, - ) - ], - ), - name: '', - ) - ], - returnType: TypeDeclaration( - baseName: 'Output', - isNullable: false, - associatedClass: emptyClass, - ), - isAsynchronous: true, - ) - ]); - final Root root = - Root(classes: [], apis: [api], enums: []); - final List classes = getCodecClasses(api, root).toList(); - expect(classes.length, 2); - expect( - classes - .where((EnumeratedClass element) => element.name == 'Input') - .length, - 1); - expect( - classes - .where((EnumeratedClass element) => element.name == 'Output') - .length, - 1); - }); - - test('get codec classes from return value type arguments', () { - final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ - Method( - name: 'doSomething', - location: ApiLocation.flutter, - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Output', - isNullable: false, - associatedClass: emptyClass, - ), - name: '', - ) - ], - returnType: TypeDeclaration( - baseName: 'List', - isNullable: false, - typeArguments: [ - TypeDeclaration( - baseName: 'Input', + test('get codec types from all classes and enums', () { + final Root root = Root(classes: [ + Class(name: 'name', fields: [ + NamedType( + name: 'name', + type: const TypeDeclaration( + baseName: 'name', isNullable: true, - associatedClass: emptyClass, - ) - ], - ), - isAsynchronous: true, - ) - ]); - final Root root = - Root(classes: [], apis: [api], enums: []); - final List classes = getCodecClasses(api, root).toList(); - expect(classes.length, 2); - expect( - classes - .where((EnumeratedClass element) => element.name == 'Input') - .length, - 1); - expect( - classes - .where((EnumeratedClass element) => element.name == 'Output') - .length, - 1); - }); - - test('get codec classes from all arguments', () { - final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ - Method( - name: 'doSomething', - location: ApiLocation.flutter, - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Foo', - isNullable: false, - associatedClass: emptyClass, - ), - name: '', - ), - Parameter( - type: TypeDeclaration( - baseName: 'Bar', - isNullable: false, - associatedEnum: emptyEnum, - ), - name: '', - ), - ], - returnType: const TypeDeclaration( - baseName: 'List', - isNullable: false, - typeArguments: [TypeDeclaration.voidDeclaration()], - ), - isAsynchronous: true, - ) + )) + ]) + ], apis: [], enums: [ + Enum(name: 'enum', members: [ + EnumMember(name: 'enumMember'), + ]) ]); - final Root root = - Root(classes: [], apis: [api], enums: []); - final List classes = getCodecClasses(api, root).toList(); - expect(classes.length, 2); - expect( - classes - .where((EnumeratedClass element) => element.name == 'Foo') - .length, - 1); - expect( - classes - .where((EnumeratedClass element) => element.name == 'Bar') - .length, - 1); + final List types = getEnumeratedTypes(root).toList(); + expect(types.length, 2); }); - test('getCodecClasses: nested type arguments', () { + test('getEnumeratedTypes:ed type arguments', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( @@ -256,22 +133,17 @@ void main() { )) ]) ], enums: []); - final List classes = - getCodecClasses(root.apis[0], root).toList(); + final List classes = getEnumeratedTypes(root).toList(); expect(classes.length, 2); expect( - classes - .where((EnumeratedClass element) => element.name == 'Foo') - .length, + classes.where((EnumeratedType element) => element.name == 'Foo').length, 1); expect( - classes - .where((EnumeratedClass element) => element.name == 'Bar') - .length, + classes.where((EnumeratedType element) => element.name == 'Bar').length, 1); }); - test('getCodecClasses: with Object', () { + test('getEnumeratedTypes: Object', () { final Root root = Root(apis: [ AstFlutterApi( name: 'Api1', @@ -300,17 +172,14 @@ void main() { type: const TypeDeclaration(baseName: 'int', isNullable: true)), ]), ], enums: []); - final List classes = - getCodecClasses(root.apis[0], root).toList(); + final List classes = getEnumeratedTypes(root).toList(); expect(classes.length, 1); expect( - classes - .where((EnumeratedClass element) => element.name == 'Foo') - .length, + classes.where((EnumeratedType element) => element.name == 'Foo').length, 1); }); - test('getCodecClasses: unique entries', () { + test('getEnumeratedTypes:ue entries', () { final Root root = Root(apis: [ AstFlutterApi( name: 'Api1', @@ -357,13 +226,10 @@ void main() { type: const TypeDeclaration(baseName: 'int', isNullable: true)), ]), ], enums: []); - final List classes = - getCodecClasses(root.apis[0], root).toList(); + final List classes = getEnumeratedTypes(root).toList(); expect(classes.length, 1); expect( - classes - .where((EnumeratedClass element) => element.name == 'Foo') - .length, + classes.where((EnumeratedType element) => element.name == 'Foo').length, 1); }); diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index d620ee6c182..3982c25150b 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -575,11 +575,7 @@ void main() { expect(code, contains('public static final class Outer')); expect(code, contains('public static final class Nested')); expect(code, contains('private @Nullable Nested nested;')); - expect( - code, - contains( - '(nested == null) ? null : Nested.fromList((ArrayList) nested)')); - expect(code, contains('add((nested == null) ? null : nested.toList());')); + expect(code, contains('add(nested);')); }); test('gen one async Host Api', () { @@ -743,12 +739,8 @@ void main() { expect(code, contains('private Enum1(final int index) {')); expect(code, contains(' this.index = index;')); - expect(code, - contains('toListResult.add(enum1 == null ? null : enum1.index);')); - expect( - code, - contains( - 'pigeonResult.setEnum1(enum1 == null ? null : Enum1.values()[(int) enum1])')); + expect(code, contains('toListResult.add(enum1);')); + expect(code, contains('pigeonResult.setEnum1((Enum1) enum1);')); }); test('primitive enum host', () { @@ -786,10 +778,13 @@ void main() { ); final String code = sink.toString(); expect(code, contains('public enum Foo')); + expect(code, + contains('return value == null ? null : Foo.values()[(int) value];')); expect( code, contains( - 'Foo fooArg = args.get(0) == null ? null : Foo.values()[(int) args.get(0)];')); + 'writeValue(stream, value == null ? null : ((Foo) value).index);')); + expect(code, contains('Foo fooArg = (Foo) args.get(0);')); }); Iterable makeIterable(String string) sync* { @@ -1516,47 +1511,7 @@ void main() { expect(code, isNot(contains('*//'))); }); - test("doesn't create codecs if no custom datatypes", () { - final Root root = Root( - apis: [ - AstFlutterApi( - name: 'Api', - methods: [ - Method( - name: 'method', - location: ApiLocation.flutter, - returnType: const TypeDeclaration.voidDeclaration(), - parameters: [ - Parameter( - name: 'field', - type: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - ), - ], - ) - ], - ) - ], - classes: [], - enums: [], - ); - final StringBuffer sink = StringBuffer(); - const JavaOptions javaOptions = JavaOptions(className: 'Messages'); - const JavaGenerator generator = JavaGenerator(); - generator.generate( - javaOptions, - root, - sink, - dartPackageName: DEFAULT_PACKAGE_NAME, - ); - final String code = sink.toString(); - expect(code, isNot(contains(' extends StandardMessageCodec'))); - expect(code, contains('StandardMessageCodec')); - }); - - test('creates custom codecs if custom datatypes present', () { + test('creates custom codecs', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index d0fcb1e3bc5..ea87f508ca1 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -51,7 +51,7 @@ void main() { final String code = sink.toString(); expect(code, contains('data class Foobar (')); expect(code, contains('val field1: Long? = null')); - expect(code, contains('fun fromList(list: List): Foobar')); + expect(code, contains('fun fromList(__pigeon_list: List): Foobar')); expect(code, contains('fun toList(): List')); }); @@ -132,9 +132,10 @@ void main() { expect(code, contains('data class Bar (')); expect(code, contains('val field1: Foo,')); expect(code, contains('val field2: String')); - expect(code, contains('fun fromList(list: List): Bar')); - expect(code, contains('val field1 = Foo.ofRaw(list[0] as Int)!!\n')); - expect(code, contains('val field2 = list[1] as String\n')); + expect(code, contains('fun fromList(__pigeon_list: List): Bar')); + expect(code, contains('Foo.ofRaw(it)')); + expect(code, contains('val field1 = __pigeon_list[0] as Foo')); + expect(code, contains('val field2 = __pigeon_list[1] as String\n')); expect(code, contains('fun toList(): List')); }); @@ -172,7 +173,7 @@ void main() { ); final String code = sink.toString(); expect(code, contains('enum class Foo(val raw: Int) {')); - expect(code, contains('val fooArg = Foo.ofRaw(args[0] as Int)')); + expect(code, contains('Foo.ofRaw(it)')); }); test('gen one host api', () { @@ -230,17 +231,20 @@ void main() { final String code = sink.toString(); expect(code, contains('interface Api')); expect(code, contains('fun doSomething(input: Input): Output')); + expect(code, contains(''' + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: Api?, messageChannelSuffix: String = "") { + ''')); expect(code, contains('channel.setMessageHandler')); expect(code, contains(''' if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val inputArg = args[0] as Input - var wrapped: List - try { - wrapped = listOf(api.doSomething(inputArg)) + val wrapped: List = try { + listOf(api.doSomething(inputArg)) } catch (exception: Throwable) { - wrapped = wrapError(exception) + wrapError(exception) } reply.reply(wrapped) } @@ -390,7 +394,7 @@ void main() { expect( code, contains( - 'val aInt = list[1].let { if (it is Int) it.toLong() else it as Long }')); + 'val aInt = __pigeon_list[1].let { num -> if (num is Int) num.toLong() else num as Long }')); expect(code, contains('val aNullableBool: Boolean? = null')); expect(code, contains('val aNullableInt: Long? = null')); expect(code, contains('val aNullableDouble: Double? = null')); @@ -402,7 +406,7 @@ void main() { expect( code, contains( - 'val aNullableInt = list[9].let { if (it is Int) it.toLong() else it as Long? }')); + 'val aNullableInt = __pigeon_list[9].let { num -> if (num is Int) num.toLong() else num as Long? }')); }); test('gen one flutter api', () { @@ -591,8 +595,8 @@ void main() { ); final String code = sink.toString(); expect(code, contains('fun doSomething(): Output')); - expect(code, contains('wrapped = listOf(api.doSomething())')); - expect(code, contains('wrapped = wrapError(exception)')); + expect(code, contains('listOf(api.doSomething())')); + expect(code, contains('wrapError(exception)')); expect(code, contains('reply(wrapped)')); }); @@ -732,10 +736,8 @@ void main() { expect(code, contains('data class Outer')); expect(code, contains('data class Nested')); expect(code, contains('val nested: Nested? = null')); - expect(code, contains('fun fromList(list: List): Outer')); - expect( - code, contains('val nested: Nested? = (list[0] as List?)?.let')); - expect(code, contains('Nested.fromList(it)')); + expect(code, contains('fun fromList(__pigeon_list: List): Outer')); + expect(code, contains('val nested = __pigeon_list[0] as Nested?')); expect(code, contains('fun toList(): List')); }); @@ -1091,7 +1093,7 @@ void main() { ); final String code = sink.toString(); expect(code, contains('fun doit(): List')); - expect(code, contains('wrapped = listOf(api.doit())')); + expect(code, contains('listOf(api.doit())')); expect(code, contains('reply.reply(wrapped)')); }); @@ -1164,12 +1166,12 @@ void main() { expect( code, contains( - 'val xArg = args[0].let { if (it is Int) it.toLong() else it as Long }')); + 'val xArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long }')); expect( code, contains( - 'val yArg = args[1].let { if (it is Int) it.toLong() else it as Long }')); - expect(code, contains('wrapped = listOf(api.add(xArg, yArg))')); + 'val yArg = args[1].let { num -> if (num is Int) num.toLong() else num as Long }')); + expect(code, contains('listOf(api.add(xArg, yArg))')); expect(code, contains('reply.reply(wrapped)')); }); @@ -1207,7 +1209,7 @@ void main() { expect( code, contains( - 'val output = it[0].let { if (it is Int) it.toLong() else it as Long }'), + 'val output = it[0].let { num -> if (num is Int) num.toLong() else num as Long }'), ); expect(code, contains('callback(Result.success(output))')); expect( @@ -1312,7 +1314,7 @@ void main() { expect( code, contains( - 'val fooArg = args[0].let { if (it is Int) it.toLong() else it as Long? }')); + 'val fooArg = args[0].let { num -> if (num is Int) num.toLong() else num as Long? }')); }); test('nullable argument flutter', () { @@ -1489,47 +1491,7 @@ void main() { expect(code, isNot(contains('*//'))); }); - test("doesn't create codecs if no custom datatypes", () { - final Root root = Root( - apis: [ - AstFlutterApi( - name: 'Api', - methods: [ - Method( - name: 'method', - location: ApiLocation.flutter, - returnType: const TypeDeclaration.voidDeclaration(), - parameters: [ - Parameter( - name: 'field', - type: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - ), - ], - ) - ], - ) - ], - classes: [], - enums: [], - ); - final StringBuffer sink = StringBuffer(); - const KotlinOptions kotlinOptions = KotlinOptions(); - const KotlinGenerator generator = KotlinGenerator(); - generator.generate( - kotlinOptions, - root, - sink, - dartPackageName: DEFAULT_PACKAGE_NAME, - ); - final String code = sink.toString(); - expect(code, isNot(contains(' : StandardMessageCodec() '))); - expect(code, contains('StandardMessageCodec')); - }); - - test('creates custom codecs if custom datatypes present', () { + test('creates custom codecs', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( @@ -1821,4 +1783,99 @@ void main() { expect(code, contains(errorClassName)); expect(code, isNot(contains('FlutterError'))); }); + + test('do not generate duplicated entries in writeValue', () { + final Root root = Root( + apis: [ + AstHostApi( + name: 'FooBar', + methods: [ + Method( + name: 'fooBar', + location: ApiLocation.host, + returnType: const TypeDeclaration.voidDeclaration(), + parameters: [ + Parameter( + name: 'bar', + type: const TypeDeclaration( + baseName: 'Bar', + isNullable: false, + ), + ), + ], + ) + ], + ) + ], + classes: [ + Class( + name: 'Foo', + fields: [ + NamedType( + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + name: 'foo', + ), + ], + ), + Class( + name: 'Bar', + fields: [ + NamedType( + type: const TypeDeclaration( + baseName: 'Foo', + isNullable: false, + ), + name: 'foo', + ), + NamedType( + type: const TypeDeclaration( + baseName: 'Foo', + isNullable: true, + ), + name: 'foo2', + ), + ], + ), + ], + enums: [], + ); + + final StringBuffer sink = StringBuffer(); + const String errorClassName = 'FooError'; + const KotlinOptions kotlinOptions = + KotlinOptions(errorClassName: errorClassName); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate( + kotlinOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + + final String code = sink.toString(); + + // Extract override fun writeValue block + final int blockStart = code.indexOf('override fun writeValue'); + expect(blockStart, isNot(-1)); + final int blockEnd = code.indexOf('super.writeValue', blockStart); + expect(blockEnd, isNot(-1)); + final String writeValueBlock = code.substring(blockStart, blockEnd); + + // Count the occurrence of 'is Foo' in the block + int count = 0; + int index = 0; + while (index != -1) { + index = writeValueBlock.indexOf('is Foo', index); + if (index != -1) { + count++; + index += 'is Foo'.length; + } + } + + // There should be only one occurrence of 'is Foo' in the block + expect(count, 1); + }); } diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index 7717f0ca877..84146be42db 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -183,7 +183,7 @@ void main() { expect( code, contains( - 'Enum1Box *enum1 = enum1AsNumber == nil ? nil : [[Enum1Box alloc] initWithValue:[enum1AsNumber integerValue]];')); + 'return enumAsNumber == nil ? nil : [[Enum1Box alloc] initWithValue:[enumAsNumber integerValue]];')); }); test('primitive enum host', () { @@ -246,7 +246,9 @@ void main() { expect( code, contains( - 'ACFoo arg_foo = [GetNullableObjectAtIndex(args, 0) integerValue];')); + 'return enumAsNumber == nil ? nil : [[ACFooBox alloc] initWithValue:[enumAsNumber integerValue]];')); + + expect(code, contains('ACFooBox * box = (ACFooBox *)value;')); } }); @@ -381,7 +383,6 @@ void main() { expect(code, contains('/// @return `nil` only when `error != nil`.')); expect(code, matches('nullable Output.*doSomething.*Input.*FlutterError')); expect(code, matches('SetUpApi.*.*_Nullable')); - expect(code, contains('ApiGetCodec(void)')); }); test('gen one api source', () { @@ -439,7 +440,6 @@ void main() { code, contains( 'NSCAssert([api respondsToSelector:@selector(doSomething:error:)')); - expect(code, contains('ApiGetCodec(void) {')); }); test('all the simple datatypes header', () { @@ -600,12 +600,8 @@ void main() { dartPackageName: DEFAULT_PACKAGE_NAME, ); final String code = sink.toString(); - expect( - code, - contains( - 'pigeonResult.nested = [Input nullableFromList:(GetNullableObjectAtIndex(list, 0))];')); - expect( - code, contains('self.nested ? [self.nested toList] : [NSNull null]')); + expect(code, + contains('pigeonResult.nested = GetNullableObjectAtIndex(list, 0);')); }); test('prefix class header', () { @@ -823,7 +819,6 @@ void main() { contains( 'initWithBinaryMessenger:(id)binaryMessenger;')); expect(code, matches('void.*doSomething.*Input.*Output')); - expect(code, contains('ApiGetCodec(void)')); }); test('gen flutter api source', () { @@ -875,7 +870,6 @@ void main() { final String code = sink.toString(); expect(code, contains('@implementation Api')); expect(code, matches('void.*doSomething.*Input.*Output.*{')); - expect(code, contains('ApiGetCodec(void) {')); }); test('gen host void header', () { @@ -2125,7 +2119,7 @@ void main() { dartPackageName: DEFAULT_PACKAGE_NAME, ); final String code = sink.toString(); - expect(code, contains('NSArray *args = message;')); + expect(code, contains('NSArray *args = message;')); expect( code, contains( @@ -2197,7 +2191,7 @@ void main() { dartPackageName: DEFAULT_PACKAGE_NAME, ); final String code = sink.toString(); - expect(code, contains('NSArray *args = message;')); + expect(code, contains('NSArray *args = message;')); expect( code, contains( @@ -2784,50 +2778,7 @@ void main() { expect(code, contains('/// ///')); }); - test("doesn't create codecs if no custom datatypes", () { - final Root root = Root( - apis: [ - AstFlutterApi( - name: 'Api', - methods: [ - Method( - name: 'method', - location: ApiLocation.flutter, - returnType: const TypeDeclaration.voidDeclaration(), - parameters: [ - Parameter( - name: 'field', - type: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - ), - ], - ) - ], - ) - ], - classes: [], - enums: [], - ); - final StringBuffer sink = StringBuffer(); - const ObjcGenerator generator = ObjcGenerator(); - final OutputFileOptions generatorOptions = - OutputFileOptions( - fileType: FileType.source, - languageOptions: const ObjcOptions(), - ); - generator.generate( - generatorOptions, - root, - sink, - dartPackageName: DEFAULT_PACKAGE_NAME, - ); - final String code = sink.toString(); - expect(code, isNot(contains(' : FlutterStandardReader'))); - }); - - test('creates custom codecs if custom datatypes present', () { + test('creates custom codecs', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 34516e8c0de..8d56fa9d66f 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -1123,28 +1123,6 @@ abstract class Api { expect(results.errors.length, 0); }); - test('Enum key not supported', () { - const String code = ''' -enum MessageKey { - title, - subtitle, - description, -} - -class Message { - int? id; - Map? additionalProperties; -} - -@HostApi() -abstract class HostApiBridge { - void sendMessage(Message message); -} -'''; - final ParseResults results = parseSource(code); - expect(results.errors.length, 1); - }); - test('Export unreferenced enums', () { const String code = ''' enum MessageKey { @@ -1213,6 +1191,36 @@ class Message { expect(options.objcOptions!.copyrightHeader, ['A', 'Header']); }); + test('@ConfigurePigeon ObjcOptions.headerIncludePath', () { + const String code = ''' +@ConfigurePigeon(PigeonOptions( + objcOptions: ObjcOptions(headerIncludePath: 'Header.path'), +)) +class Message { + int? id; +} +'''; + + final ParseResults results = parseSource(code); + final PigeonOptions options = PigeonOptions.fromMap(results.pigeonOptions!); + expect(options.objcOptions?.headerIncludePath, 'Header.path'); + }); + + test('@ConfigurePigeon CppOptions.headerIncludePath', () { + const String code = ''' +@ConfigurePigeon(PigeonOptions( + cppOptions: CppOptions(headerIncludePath: 'Header.path'), +)) +class Message { + int? id; +} +'''; + + final ParseResults results = parseSource(code); + final PigeonOptions options = PigeonOptions.fromMap(results.pigeonOptions!); + expect(options.cppOptions?.headerIncludePath, 'Header.path'); + }); + test('return nullable', () { const String code = ''' @HostApi() diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index a4eb67427b9..6ffe4f4d9d7 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -50,7 +50,8 @@ void main() { final String code = sink.toString(); expect(code, contains('struct Foobar')); expect(code, contains('var field1: Int64? = nil')); - expect(code, contains('static func fromList(_ list: [Any?]) -> Foobar?')); + expect(code, + contains('static func fromList(_ __pigeon_list: [Any?]) -> Foobar?')); expect(code, contains('func toList() -> [Any?]')); expect(code, isNot(contains('if ('))); }); @@ -118,7 +119,12 @@ void main() { ); final String code = sink.toString(); expect(code, contains('enum Foo: Int')); - expect(code, contains('let fooArg = Foo(rawValue: args[0] as! Int)!')); + expect( + code, + contains( + 'let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int)')); + expect(code, contains('enumResult = Foo(rawValue: enumResultAsInt)')); + expect(code, contains('let fooArg = args[0] as! Foo')); expect(code, isNot(contains('if ('))); }); @@ -394,7 +400,7 @@ void main() { ); final String code = sink.toString(); expect(code, - contains('completion: @escaping (Result) -> Void')); + contains('completion: @escaping (Result) -> Void')); expect(code, contains('completion(.success(Void()))')); expect(code, isNot(contains('if ('))); }); @@ -476,7 +482,7 @@ void main() { expect( code, contains( - 'func doSomething(completion: @escaping (Result) -> Void)')); + 'func doSomething(completion: @escaping (Result) -> Void)')); expect(code, contains('channel.sendMessage(nil')); expect(code, isNot(contains('if ('))); }); @@ -575,8 +581,10 @@ void main() { expect(code, contains('struct Outer')); expect(code, contains('struct Nested')); expect(code, contains('var nested: Nested? = nil')); - expect(code, contains('static func fromList(_ list: [Any?]) -> Outer?')); - expect(code, contains('nested = Nested.fromList(nestedList)')); + expect(code, + contains('static func fromList(_ __pigeon_list: [Any?]) -> Outer?')); + expect( + code, contains('let nested: Nested? = nilOrValue(__pigeon_list[0])')); expect(code, contains('func toList() -> [Any?]')); expect(code, isNot(contains('if ('))); // Single-element list serializations should not have a trailing comma. @@ -961,7 +969,7 @@ void main() { expect( code, contains( - 'func doit(completion: @escaping (Result<[Int64?], FlutterError>) -> Void)')); + 'func doit(completion: @escaping (Result<[Int64?], PigeonError>) -> Void)')); expect(code, contains('let result = listResponse[0] as! [Int64?]')); expect(code, contains('completion(.success(result))')); }); @@ -1049,7 +1057,7 @@ void main() { expect( code, contains( - 'func add(x xArg: Int64, y yArg: Int64, completion: @escaping (Result) -> Void)')); + 'func add(x xArg: Int64, y yArg: Int64, completion: @escaping (Result) -> Void)')); expect(code, contains('channel.sendMessage([xArg, yArg] as [Any?]) { response in')); }); @@ -1189,7 +1197,7 @@ void main() { expect( code, contains( - 'func doit(foo fooArg: Int64?, completion: @escaping (Result) -> Void)')); + 'func doit(foo fooArg: Int64?, completion: @escaping (Result) -> Void)')); }); test('nonnull fields', () { @@ -1323,46 +1331,7 @@ void main() { expect(code, contains('/// ///')); }); - test("doesn't create codecs if no custom datatypes", () { - final Root root = Root( - apis: [ - AstFlutterApi( - name: 'Api', - methods: [ - Method( - name: 'method', - location: ApiLocation.flutter, - returnType: const TypeDeclaration.voidDeclaration(), - parameters: [ - Parameter( - name: 'field', - type: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - ), - ], - ) - ], - ) - ], - classes: [], - enums: [], - ); - final StringBuffer sink = StringBuffer(); - const SwiftOptions swiftOptions = SwiftOptions(); - const SwiftGenerator generator = SwiftGenerator(); - generator.generate( - swiftOptions, - root, - sink, - dartPackageName: DEFAULT_PACKAGE_NAME, - ); - final String code = sink.toString(); - expect(code, isNot(contains(': FlutterStandardReader '))); - }); - - test('creates custom codecs if custom datatypes present', () { + test('creates custom codecs', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ Method( @@ -1569,6 +1538,6 @@ void main() { expect( code, contains( - 'return FlutterError(code: "channel-error", message: "Unable to establish connection on channel: \'\\(channelName)\'.", details: "")')); + 'return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: \'\\(channelName)\'.", details: "")')); }); } diff --git a/packages/pigeon/tool/shared/generation.dart b/packages/pigeon/tool/shared/generation.dart index 1cd3b6e5ea9..8166e49d73f 100644 --- a/packages/pigeon/tool/shared/generation.dart +++ b/packages/pigeon/tool/shared/generation.dart @@ -89,10 +89,18 @@ Future generateTestPigeons({required String baseDir}) async { ? 'FlutterError' : '${pascalCaseName}Error'; + final bool swiftErrorUseDefaultErrorName = input == 'core_tests'; + + final String? swiftErrorClassName = + swiftErrorUseDefaultErrorName ? null : '${pascalCaseName}Error'; + // Generate the default language test plugin output. int generateCode = await runPigeon( input: './pigeons/$input.dart', dartOut: '$sharedDartOutputBase/lib/src/generated/$input.gen.dart', + dartTestOut: input == 'message' + ? '$sharedDartOutputBase/test/test_message.gen.dart' + : null, // Android kotlinOut: skipLanguages.contains(GeneratorLanguage.kotlin) ? null @@ -104,6 +112,7 @@ Future generateTestPigeons({required String baseDir}) async { swiftOut: skipLanguages.contains(GeneratorLanguage.swift) ? null : '$outputBase/ios/Classes/$pascalCaseName.gen.swift', + swiftErrorClassName: swiftErrorClassName, // Windows cppHeaderOut: skipLanguages.contains(GeneratorLanguage.cpp) ? null @@ -127,6 +136,7 @@ Future generateTestPigeons({required String baseDir}) async { swiftOut: skipLanguages.contains(GeneratorLanguage.swift) ? null : '$outputBase/macos/Classes/$pascalCaseName.gen.swift', + swiftErrorClassName: swiftErrorClassName, suppressVersion: true, dartPackageName: 'pigeon_integration_tests', ); @@ -188,6 +198,7 @@ Future runPigeon({ String? kotlinErrorClassName, bool kotlinIncludeErrorClass = true, String? swiftOut, + String? swiftErrorClassName, String? cppHeaderOut, String? cppSourceOut, String? cppNamespace, @@ -236,7 +247,9 @@ Future runPigeon({ objcSourceOut: objcSourceOut, objcOptions: ObjcOptions(prefix: objcPrefix), swiftOut: swiftOut, - swiftOptions: const SwiftOptions(), + swiftOptions: SwiftOptions( + errorClassName: swiftErrorClassName, + ), basePath: basePath, dartPackageName: dartPackageName, )); diff --git a/packages/pigeon/tool/shared/test_runner.dart b/packages/pigeon/tool/shared/test_runner.dart index c2df4b58f06..01f12cb26fd 100644 --- a/packages/pigeon/tool/shared/test_runner.dart +++ b/packages/pigeon/tool/shared/test_runner.dart @@ -49,6 +49,7 @@ Future runTests( print('# Running $test'); final int testCode = await info.function(); if (testCode != 0) { + print('# Failed, exit code: $testCode'); exit(testCode); } print(''); diff --git a/packages/platform/.gitignore b/packages/platform/.gitignore deleted file mode 100644 index dfbf1b43f03..00000000000 --- a/packages/platform/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -### Dart template -# Don’t commit the following directories created by pub. -.buildlog -.dart_tool/ -.pub/ -build/ -packages -.packages - -# Include when developing application packages. -pubspec.lock - -# IDE -.project -.settings -.idea -.c9 diff --git a/packages/platform/AUTHORS b/packages/platform/AUTHORS deleted file mode 100644 index ad59f118417..00000000000 --- a/packages/platform/AUTHORS +++ /dev/null @@ -1,6 +0,0 @@ -# Below is a list of people and organizations that have contributed -# to the Process project. Names should be added to the list like so: -# -# Name/Organization - -Google Inc. diff --git a/packages/platform/CHANGELOG.md b/packages/platform/CHANGELOG.md deleted file mode 100644 index 0cbbc128d1c..00000000000 --- a/packages/platform/CHANGELOG.md +++ /dev/null @@ -1,105 +0,0 @@ -## NEXT - -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. - -## 3.1.4 - -* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. -* Fixes new lint warnings. - -## 3.1.3 - -* Adds example app. - -## 3.1.2 - -* Adds pub topics to package metadata. -* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. - -## 3.1.1 - -* Transfers the package source from https://github.com/google/platform.dart to - https://github.com/flutter/packages. - -## 3.1.0 - -* Removed `Platform.packageRoot`, which was already marked deprecated, and which - didn't work in Dart 2. - -## 3.0.2 - -* Added `FakePlatform.copyWith` function. - -## 3.0.1 - -* Added string constants for each of the supported platforms for use in switch - statements. - -## 3.0.0 - -* First stable null safe release. - -## 3.0.0-nullsafety.4 - -* Update supported SDK range. - -## 3.0.0-nullsafety.3 - -* Update supported SDK range. - -## 3.0.0-nullsafety.2 - -* Update supported SDK range. - -## 3.0.0-nullsafety.1 - -* Migrate package to null-safe dart. - -## 2.2.1 - -* Add `operatingSystemVersion` - -## 2.2.0 - -* Declare compatibility with Dart 2 stable -* Update dependency on `package:test` to 1.0 - -## 2.1.2 - -* Relax sdk upper bound constraint to '<2.0.0' to allow 'edge' dart sdk use. - -## 2.1.1 - -* Bumped maximum Dart SDK version to 2.0.0-dev.infinity - -## 2.1.0 - -* Added `localeName` -* Bumped minimum Dart SDK version to 1.24.0-dev.0.0 - -## 2.0.0 - -* Added `stdinSupportsAnsi` and `stdinSupportsAnsi` -* Removed `ansiSupported` - -## 1.1.1 - -* Updated `LocalPlatform` to use new `dart.io` API for ansi color support queries -* Bumped minimum Dart SDK version to 1.23.0-dev.10.0 - -## 1.1.0 - -* Added `ansiSupported` -* Bumped minimum Dart SDK version to 1.23.0-dev.9.0 - -## 1.0.2 - -* Minor doc updates - -## 1.0.1 - -* Added const constructors for `Platform` and `LocalPlatform` - -## 1.0.0 - -* Initial version diff --git a/packages/platform/LICENSE b/packages/platform/LICENSE deleted file mode 100644 index c6823b81eb8..00000000000 --- a/packages/platform/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/platform/README.md b/packages/platform/README.md index 44932a67231..e78cea77ebd 100644 --- a/packages/platform/README.md +++ b/packages/platform/README.md @@ -1,10 +1,2 @@ -[![Pub](https://img.shields.io/pub/v/platform.svg)](https://pub.dartlang.org/packages/platform) - -A generic platform abstraction for Dart. - -Like `dart:io`, `package:platform` supplies a rich, Dart-idiomatic API for -accessing platform-specific information. - -`package:platform` provides a lightweight wrapper around the static `Platform` -properties that exist in `dart:io`. However, it uses instance properties rather -than static properties, making it possible to mock out in tests. +The source code for `package:platform` has been moved to the +Dart GitHub org: https://github.com/dart-lang/platform/ diff --git a/packages/platform/dart_test.yaml b/packages/platform/dart_test.yaml deleted file mode 100644 index 91ec220b8e2..00000000000 --- a/packages/platform/dart_test.yaml +++ /dev/null @@ -1 +0,0 @@ -test_on: vm diff --git a/packages/platform/example/.gitignore b/packages/platform/example/.gitignore deleted file mode 100644 index 24476c5d1eb..00000000000 --- a/packages/platform/example/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/packages/platform/example/.metadata b/packages/platform/example/.metadata deleted file mode 100644 index e2ca92f1456..00000000000 --- a/packages/platform/example/.metadata +++ /dev/null @@ -1,42 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - - platform: android - create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - - platform: ios - create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - - platform: linux - create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - - platform: macos - create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - - platform: windows - create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/platform/example/android/.gitignore b/packages/platform/example/android/.gitignore deleted file mode 100644 index 6f568019d3c..00000000000 --- a/packages/platform/example/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks diff --git a/packages/platform/example/android/app/build.gradle b/packages/platform/example/android/app/build.gradle deleted file mode 100644 index 94702dbb807..00000000000 --- a/packages/platform/example/android/app/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -android { - namespace "dev.flutter.plaform_example" - compileSdk flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - applicationId "dev.flutter.plaform_example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies {} diff --git a/packages/platform/example/android/app/src/debug/AndroidManifest.xml b/packages/platform/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f6981d5d..00000000000 --- a/packages/platform/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/platform/example/android/app/src/main/AndroidManifest.xml b/packages/platform/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 4feed7764fa..00000000000 --- a/packages/platform/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/platform/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/platform/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index 812458bad19..00000000000 --- a/packages/platform/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/platform/example/android/app/src/main/res/drawable/launch_background.xml b/packages/platform/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 5782842b813..00000000000 --- a/packages/platform/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/platform/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/platform/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0..00000000000 Binary files a/packages/platform/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/platform/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/platform/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8..00000000000 Binary files a/packages/platform/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/platform/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/platform/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d4391482b..00000000000 Binary files a/packages/platform/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/platform/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/platform/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7..00000000000 Binary files a/packages/platform/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/platform/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/platform/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb..00000000000 Binary files a/packages/platform/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/platform/example/android/app/src/main/res/values-night/styles.xml b/packages/platform/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be745f..00000000000 --- a/packages/platform/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/platform/example/android/app/src/main/res/values/styles.xml b/packages/platform/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88056e..00000000000 --- a/packages/platform/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/platform/example/android/app/src/profile/AndroidManifest.xml b/packages/platform/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981d5d..00000000000 --- a/packages/platform/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/platform/example/android/build.gradle b/packages/platform/example/android/build.gradle deleted file mode 100644 index 39a10a52cbe..00000000000 --- a/packages/platform/example/android/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. - def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' - if (System.getenv().containsKey(artifactRepoKey)) { - println "Using artifact hub" - maven { url System.getenv(artifactRepoKey) } - } - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/packages/platform/example/android/gradle.properties b/packages/platform/example/android/gradle.properties deleted file mode 100644 index 94adc3a3f97..00000000000 --- a/packages/platform/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/platform/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/platform/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e1ca574ef01..00000000000 --- a/packages/platform/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/packages/platform/example/android/settings.gradle b/packages/platform/example/android/settings.gradle deleted file mode 100644 index 818db641d69..00000000000 --- a/packages/platform/example/android/settings.gradle +++ /dev/null @@ -1,31 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() - - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") - - plugins { - id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false - } -} - -include ":app" - -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. -buildscript { - repositories { - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" - } -} -apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" diff --git a/packages/platform/example/dart_test.yaml b/packages/platform/example/dart_test.yaml deleted file mode 100644 index 91ec220b8e2..00000000000 --- a/packages/platform/example/dart_test.yaml +++ /dev/null @@ -1 +0,0 @@ -test_on: vm diff --git a/packages/platform/example/ios/.gitignore b/packages/platform/example/ios/.gitignore deleted file mode 100644 index 7a7f9873ad7..00000000000 --- a/packages/platform/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/platform/example/ios/Flutter/AppFrameworkInfo.plist b/packages/platform/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 7c569640062..00000000000 --- a/packages/platform/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 12.0 - - diff --git a/packages/platform/example/ios/Flutter/Debug.xcconfig b/packages/platform/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee85b8..00000000000 --- a/packages/platform/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/platform/example/ios/Flutter/Release.xcconfig b/packages/platform/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee85b8..00000000000 --- a/packages/platform/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/platform/example/ios/Runner.xcodeproj/project.pbxproj b/packages/platform/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 7bdf99646ef..00000000000 --- a/packages/platform/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,488 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = SQN8R5V84L; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plaformExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = SQN8R5V84L; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plaformExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = SQN8R5V84L; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plaformExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a6254..00000000000 --- a/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5ea1..00000000000 --- a/packages/platform/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/platform/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/platform/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16ed0..00000000000 --- a/packages/platform/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/platform/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/platform/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/platform/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/platform/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/platform/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5ea1..00000000000 --- a/packages/platform/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/platform/example/ios/Runner/AppDelegate.swift b/packages/platform/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index d83c0ff0bee..00000000000 --- a/packages/platform/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,17 +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 Flutter -import UIKit - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2d9..00000000000 --- a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 7353c41ecf9..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e11..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 321773cd857..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 797d452e458..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 0ec30343922..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec30343922..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c7..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 84ac32ae7d9..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 8953cba0906..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd46..00000000000 --- a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3..00000000000 Binary files a/packages/platform/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/packages/platform/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/platform/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c93..00000000000 --- a/packages/platform/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/platform/example/ios/Runner/Base.lproj/Main.storyboard b/packages/platform/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516fb3..00000000000 --- a/packages/platform/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/platform/example/lib/main.dart b/packages/platform/example/lib/main.dart deleted file mode 100644 index 61955cb03ac..00000000000 --- a/packages/platform/example/lib/main.dart +++ /dev/null @@ -1,147 +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 'package:flutter/material.dart'; -import 'package:platform/platform.dart'; - -/// This sample app shows the platform details of the device it is running on. -void main() => runApp(const MyApp()); - -/// The main app. -class MyApp extends StatelessWidget { - /// Constructs a [MyApp] - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - const LocalPlatform platform = LocalPlatform(); - return MaterialApp( - title: 'Platform Example', - home: Scaffold( - appBar: AppBar( - title: const Text('Platform Example'), - ), - body: Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FormatDetails( - title: 'Operating System:', - value: platform.operatingSystem, - ), - FormatDetails( - title: 'Number of Processors:', - value: platform.numberOfProcessors.toString(), - ), - FormatDetails( - title: 'Path Separator:', - value: platform.pathSeparator, - ), - FormatDetails( - title: 'Local Hostname:', - value: platform.localHostname, - ), - FormatDetails( - title: 'Environment:', - value: platform.environment.toString(), - ), - FormatDetails( - title: 'Executable:', - value: platform.executable, - ), - FormatDetails( - title: 'Resolved Executable:', - value: platform.resolvedExecutable, - ), - FormatDetails( - title: 'Script:', - value: platform.script.toString(), - ), - FormatDetails( - title: 'Executable Arguments:', - value: platform.executableArguments.toString(), - ), - FormatDetails( - title: 'Package Config:', - value: platform.packageConfig.toString(), - ), - FormatDetails( - title: 'Version:', - value: platform.version, - ), - FormatDetails( - title: 'Stdin Supports ANSI:', - value: platform.stdinSupportsAnsi.toString(), - ), - FormatDetails( - title: 'Stdout Supports ANSI:', - value: platform.stdoutSupportsAnsi.toString(), - ), - FormatDetails( - title: 'Locale Name:', - value: platform.localeName, - ), - FormatDetails( - title: 'isAndroid:', - value: platform.isAndroid.toString(), - ), - FormatDetails( - title: 'isFuchsia:', - value: platform.isFuchsia.toString(), - ), - FormatDetails( - title: 'isIOS:', - value: platform.isIOS.toString(), - ), - FormatDetails( - title: 'isLinux:', - value: platform.isLinux.toString(), - ), - FormatDetails( - title: 'isMacOS:', - value: platform.isMacOS.toString(), - ), - FormatDetails( - title: 'isWindows:', - value: platform.isWindows.toString(), - ), - ], - ), - ), - ), - ), - ); - } -} - -/// A widget to format the details. -class FormatDetails extends StatelessWidget { - /// Constructs a [FormatDetails]. - const FormatDetails({ - super.key, - required this.title, - required this.value, - }); - - /// The title of the field. - final String title; - - /// The value of the field. - final String value; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Text( - title, - style: Theme.of(context).textTheme.titleLarge, - ), - Text(value), - const SizedBox(height: 20), - ], - ); - } -} diff --git a/packages/platform/example/linux/.gitignore b/packages/platform/example/linux/.gitignore deleted file mode 100644 index d3896c98444..00000000000 --- a/packages/platform/example/linux/.gitignore +++ /dev/null @@ -1 +0,0 @@ -flutter/ephemeral diff --git a/packages/platform/example/linux/CMakeLists.txt b/packages/platform/example/linux/CMakeLists.txt deleted file mode 100644 index b545aabce69..00000000000 --- a/packages/platform/example/linux/CMakeLists.txt +++ /dev/null @@ -1,139 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.10) -project(runner LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "plaform_example") -# The unique GTK application identifier for this application. See: -# https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "dev.flutter.plaform_example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(SET CMP0063 NEW) - -# Load bundled libraries from the lib/ directory relative to the binary. -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endif() - -# Define build configuration options. -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") -endif() - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_14) - target_compile_options(${TARGET} PRIVATE -Wall -Werror) - target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") - target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) - -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Define the application target. To change its name, change BINARY_NAME above, -# not the value here, or `flutter run` will no longer work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) - -# Only the install-generated bundle's copy of the executable will launch -# correctly, since the resources must in the right relative locations. To avoid -# people trying to run the unbundled copy, put it in a subdirectory instead of -# the default top-level location. -set_target_properties(${BINARY_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" -) - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# By default, "installing" just makes a relocatable bundle in the build -# directory. -set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -# Start with a clean build bundle directory every time. -install(CODE " - file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") - " COMPONENT Runtime) - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endforeach(bundled_library) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() diff --git a/packages/platform/example/linux/flutter/CMakeLists.txt b/packages/platform/example/linux/flutter/CMakeLists.txt deleted file mode 100644 index d5bd01648a9..00000000000 --- a/packages/platform/example/linux/flutter/CMakeLists.txt +++ /dev/null @@ -1,88 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.10) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. - -# Serves the same purpose as list(TRANSFORM ... PREPEND ...), -# which isn't available in 3.10. -function(list_prepend LIST_NAME PREFIX) - set(NEW_LIST "") - foreach(element ${${LIST_NAME}}) - list(APPEND NEW_LIST "${PREFIX}${element}") - endforeach(element) - set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) -endfunction() - -# === Flutter Library === -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) -pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) - -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "fl_basic_message_channel.h" - "fl_binary_codec.h" - "fl_binary_messenger.h" - "fl_dart_project.h" - "fl_engine.h" - "fl_json_message_codec.h" - "fl_json_method_codec.h" - "fl_message_codec.h" - "fl_method_call.h" - "fl_method_channel.h" - "fl_method_codec.h" - "fl_method_response.h" - "fl_plugin_registrar.h" - "fl_plugin_registry.h" - "fl_standard_message_codec.h" - "fl_standard_method_codec.h" - "fl_string_codec.h" - "fl_value.h" - "fl_view.h" - "flutter_linux.h" -) -list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") -target_link_libraries(flutter INTERFACE - PkgConfig::GTK - PkgConfig::GLIB - PkgConfig::GIO -) -add_dependencies(flutter flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CMAKE_CURRENT_BINARY_DIR}/_phony_ - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" - ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} -) diff --git a/packages/platform/example/linux/flutter/generated_plugins.cmake b/packages/platform/example/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb..00000000000 --- a/packages/platform/example/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/packages/platform/example/linux/main.cc b/packages/platform/example/linux/main.cc deleted file mode 100644 index 1507d02825e..00000000000 --- a/packages/platform/example/linux/main.cc +++ /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. - -#include "my_application.h" - -int main(int argc, char** argv) { - g_autoptr(MyApplication) app = my_application_new(); - return g_application_run(G_APPLICATION(app), argc, argv); -} diff --git a/packages/platform/example/linux/my_application.cc b/packages/platform/example/linux/my_application.cc deleted file mode 100644 index 492f64f8b00..00000000000 --- a/packages/platform/example/linux/my_application.cc +++ /dev/null @@ -1,111 +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. - -#include "my_application.h" - -#include -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#include "flutter/generated_plugin_registrant.h" - -struct _MyApplication { - GtkApplication parent_instance; - char** dart_entrypoint_arguments; -}; - -G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) - -// Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = - GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); - - // Use a header bar when running in GNOME as this is the common style used - // by applications and is the setup most users will be using (e.g. Ubuntu - // desktop). - // If running on X and not using GNOME then just use a traditional title bar - // in case the window manager does more exotic layout, e.g. tiling. - // If running on Wayland assume the header bar will work (may need changing - // if future cases occur). - gboolean use_header_bar = TRUE; -#ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; - } - } -#endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); - gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "plaform_example"); - gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "plaform_example"); - } - - gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); - - g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments( - project, self->dart_entrypoint_arguments); - - FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - - fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus(GTK_WIDGET(view)); -} - -// Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, - gchar*** arguments, - int* exit_status) { - MyApplication* self = MY_APPLICATION(application); - // Strip out the first argument as it is the binary name. - self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); - - g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; - } - - g_application_activate(application); - *exit_status = 0; - - return TRUE; -} - -// Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); - g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); - G_OBJECT_CLASS(my_application_parent_class)->dispose(object); -} - -static void my_application_class_init(MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = - my_application_local_command_line; - G_OBJECT_CLASS(klass)->dispose = my_application_dispose; -} - -static void my_application_init(MyApplication* self) {} - -MyApplication* my_application_new() { - return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, "flags", - G_APPLICATION_NON_UNIQUE, nullptr)); -} diff --git a/packages/platform/example/linux/my_application.h b/packages/platform/example/linux/my_application.h deleted file mode 100644 index 6e9f0c3ff66..00000000000 --- a/packages/platform/example/linux/my_application.h +++ /dev/null @@ -1,22 +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. - -#ifndef FLUTTER_MY_APPLICATION_H_ -#define FLUTTER_MY_APPLICATION_H_ - -#include - -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, - GtkApplication) - -/** - * my_application_new: - * - * Creates a new Flutter-based application. - * - * Returns: a new #MyApplication. - */ -MyApplication* my_application_new(); - -#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/platform/example/macos/.gitignore b/packages/platform/example/macos/.gitignore deleted file mode 100644 index 746adbb6b9e..00000000000 --- a/packages/platform/example/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/packages/platform/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/platform/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index c2efd0b608b..00000000000 --- a/packages/platform/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/platform/example/macos/Flutter/Flutter-Release.xcconfig b/packages/platform/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index c2efd0b608b..00000000000 --- a/packages/platform/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/platform/example/macos/Runner.xcodeproj/project.pbxproj b/packages/platform/example/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index a9a2b85abfc..00000000000 --- a/packages/platform/example/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,573 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* plaform_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = plaform_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* plaform_example.app */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* plaform_example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/packages/platform/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/platform/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/platform/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/platform/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/platform/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index fde7c06f581..00000000000 --- a/packages/platform/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/platform/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/platform/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d6..00000000000 --- a/packages/platform/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/platform/example/macos/Runner/AppDelegate.swift b/packages/platform/example/macos/Runner/AppDelegate.swift deleted file mode 100644 index 5cec4c48f62..00000000000 --- a/packages/platform/example/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +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 Cocoa -import FlutterMacOS - -@NSApplicationMain -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } -} diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a2ec33f19f1..00000000000 --- a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 82b6f9d9a33..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100644 index 13b35eba55c..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100644 index 0a3f5fa40fb..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png deleted file mode 100644 index bdb57226d5f..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png deleted file mode 100644 index f083318e09c..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png deleted file mode 100644 index 326c0e72c9d..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100644 index 2f1632cfddf..00000000000 Binary files a/packages/platform/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and /dev/null differ diff --git a/packages/platform/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/platform/example/macos/Runner/Base.lproj/MainMenu.xib deleted file mode 100644 index 80e867a4e06..00000000000 --- a/packages/platform/example/macos/Runner/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/platform/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/platform/example/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index 31b5b4daff9..00000000000 --- a/packages/platform/example/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = plaform_example - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plaformExample - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2023 dev.flutter. All rights reserved. diff --git a/packages/platform/example/macos/Runner/Configs/Debug.xcconfig b/packages/platform/example/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd9464f..00000000000 --- a/packages/platform/example/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/packages/platform/example/macos/Runner/Configs/Release.xcconfig b/packages/platform/example/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f49561c..00000000000 --- a/packages/platform/example/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/packages/platform/example/macos/Runner/Configs/Warnings.xcconfig b/packages/platform/example/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf4780b..00000000000 --- a/packages/platform/example/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/platform/example/macos/Runner/DebugProfile.entitlements b/packages/platform/example/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index dddb8a30c85..00000000000 --- a/packages/platform/example/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/packages/platform/example/macos/Runner/MainFlutterWindow.swift b/packages/platform/example/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index f21908966e9..00000000000 --- a/packages/platform/example/macos/Runner/MainFlutterWindow.swift +++ /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 Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/packages/platform/example/macos/Runner/Release.entitlements b/packages/platform/example/macos/Runner/Release.entitlements deleted file mode 100644 index 852fa1a4728..00000000000 --- a/packages/platform/example/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/packages/platform/example/pubspec.yaml b/packages/platform/example/pubspec.yaml deleted file mode 100644 index 8991a8c6188..00000000000 --- a/packages/platform/example/pubspec.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: plaform_example -description: Demonstrates how to use the plaform plugin. -publish_to: 'none' -version: 1.0.0+1 - -environment: - sdk: ^3.1.0 - -dependencies: - flutter: - sdk: flutter - platform: - path: ../ - -dev_dependencies: - flutter_test: - sdk: flutter - -flutter: - uses-material-design: true diff --git a/packages/platform/example/test/widget_test.dart b/packages/platform/example/test/widget_test.dart deleted file mode 100644 index dc9e72d861f..00000000000 --- a/packages/platform/example/test/widget_test.dart +++ /dev/null @@ -1,18 +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 'package:flutter_test/flutter_test.dart'; - -import 'package:plaform_example/main.dart'; - -void main() { - testWidgets('smoke test', (WidgetTester tester) async { - await tester.pumpWidget(const MyApp()); - - expect(find.text('Platform Example'), findsOneWidget); - expect(find.text('Operating System:'), findsOneWidget); - expect(find.text('Number of Processors:'), findsOneWidget); - expect(find.text('Path Separator:'), findsOneWidget); - }); -} diff --git a/packages/platform/example/windows/.gitignore b/packages/platform/example/windows/.gitignore deleted file mode 100644 index d492d0d98c8..00000000000 --- a/packages/platform/example/windows/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ diff --git a/packages/platform/example/windows/CMakeLists.txt b/packages/platform/example/windows/CMakeLists.txt deleted file mode 100644 index ea6baa44d9f..00000000000 --- a/packages/platform/example/windows/CMakeLists.txt +++ /dev/null @@ -1,102 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(plaform_example LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "plaform_example") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(VERSION 3.14...3.25) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) diff --git a/packages/platform/example/windows/flutter/CMakeLists.txt b/packages/platform/example/windows/flutter/CMakeLists.txt deleted file mode 100644 index 903f4899d6f..00000000000 --- a/packages/platform/example/windows/flutter/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) diff --git a/packages/platform/example/windows/flutter/generated_plugins.cmake b/packages/platform/example/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c16..00000000000 --- a/packages/platform/example/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/packages/platform/example/windows/runner/CMakeLists.txt b/packages/platform/example/windows/runner/CMakeLists.txt deleted file mode 100644 index 394917c053a..00000000000 --- a/packages/platform/example/windows/runner/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/platform/example/windows/runner/Runner.rc b/packages/platform/example/windows/runner/Runner.rc deleted file mode 100644 index 9ec55464631..00000000000 --- a/packages/platform/example/windows/runner/Runner.rc +++ /dev/null @@ -1,121 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "dev.flutter" "\0" - VALUE "FileDescription", "plaform_example" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "plaform_example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2023 dev.flutter. All rights reserved." "\0" - VALUE "OriginalFilename", "plaform_example.exe" "\0" - VALUE "ProductName", "plaform_example" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/packages/platform/example/windows/runner/flutter_window.cpp b/packages/platform/example/windows/runner/flutter_window.cpp deleted file mode 100644 index 081067d2a5f..00000000000 --- a/packages/platform/example/windows/runner/flutter_window.cpp +++ /dev/null @@ -1,73 +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. - -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - - flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); - - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} diff --git a/packages/platform/example/windows/runner/flutter_window.h b/packages/platform/example/windows/runner/flutter_window.h deleted file mode 100644 index f1fc669093d..00000000000 --- a/packages/platform/example/windows/runner/flutter_window.h +++ /dev/null @@ -1,37 +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. - -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/platform/example/windows/runner/main.cpp b/packages/platform/example/windows/runner/main.cpp deleted file mode 100644 index 5aed350ab7c..00000000000 --- a/packages/platform/example/windows/runner/main.cpp +++ /dev/null @@ -1,46 +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. - -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t* command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"plaform_example", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} diff --git a/packages/platform/example/windows/runner/resource.h b/packages/platform/example/windows/runner/resource.h deleted file mode 100644 index d5d958dc425..00000000000 --- a/packages/platform/example/windows/runner/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/packages/platform/example/windows/runner/resources/app_icon.ico b/packages/platform/example/windows/runner/resources/app_icon.ico deleted file mode 100644 index c04e20caf63..00000000000 Binary files a/packages/platform/example/windows/runner/resources/app_icon.ico and /dev/null differ diff --git a/packages/platform/example/windows/runner/runner.exe.manifest b/packages/platform/example/windows/runner/runner.exe.manifest deleted file mode 100644 index a42ea7687cb..00000000000 --- a/packages/platform/example/windows/runner/runner.exe.manifest +++ /dev/null @@ -1,20 +0,0 @@ - - - - - PerMonitorV2 - - - - - - - - - - - - - - - diff --git a/packages/platform/example/windows/runner/utils.cpp b/packages/platform/example/windows/runner/utils.cpp deleted file mode 100644 index 8269e813bba..00000000000 --- a/packages/platform/example/windows/runner/utils.cpp +++ /dev/null @@ -1,69 +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. - -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE* unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - int target_length = - ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, - nullptr, 0, nullptr, nullptr) - - 1; // remove the trailing null character - int input_length = (int)wcslen(utf16_string); - std::string utf8_string; - if (target_length <= 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, - utf8_string.data(), target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} diff --git a/packages/platform/example/windows/runner/utils.h b/packages/platform/example/windows/runner/utils.h deleted file mode 100644 index bd81e1e0233..00000000000 --- a/packages/platform/example/windows/runner/utils.h +++ /dev/null @@ -1,23 +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. - -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ diff --git a/packages/platform/example/windows/runner/win32_window.cpp b/packages/platform/example/windows/runner/win32_window.cpp deleted file mode 100644 index 98c69c665c7..00000000000 --- a/packages/platform/example/windows/runner/win32_window.cpp +++ /dev/null @@ -1,284 +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. - -#include "win32_window.h" - -#include -#include - -#include "resource.h" - -namespace { - -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: -/// https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute -#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 -#endif - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = - L"AppsUseLightTheme"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - FreeLibrary(user32_module); -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { ++g_active_window_count; } - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::Create(const std::wstring& title, const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - UpdateTheme(window); - - return OnCreate(); -} - -bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { return window_handle_; } - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} - -void Win32Window::UpdateTheme(HWND const window) { - DWORD light_mode; - DWORD light_mode_size = sizeof(light_mode); - LSTATUS result = - RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, - &light_mode, &light_mode_size); - - if (result == ERROR_SUCCESS) { - BOOL enable_dark_mode = light_mode == 0; - DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); - } -} diff --git a/packages/platform/example/windows/runner/win32_window.h b/packages/platform/example/windows/runner/win32_window.h deleted file mode 100644 index 6b5a657aad0..00000000000 --- a/packages/platform/example/windows/runner/win32_window.h +++ /dev/null @@ -1,104 +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. - -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates a win32 window with |title| that is positioned and sized using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size this function will scale the inputted width and height as - // as appropriate for the default monitor. The window is invisible until - // |Show| is called. Returns true if the window was created successfully. - bool Create(const std::wstring& title, const Point& origin, const Size& size); - - // Show the current window. Returns true if the window was successfully shown. - bool Show(); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - // Update the window frame's theme to match the system theme. - static void UpdateTheme(HWND const window); - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/platform/lib/platform.dart b/packages/platform/lib/platform.dart deleted file mode 100644 index f096dcb410e..00000000000 --- a/packages/platform/lib/platform.dart +++ /dev/null @@ -1,8 +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. - -// Core interfaces & classes. -export 'src/interface/local_platform.dart'; -export 'src/interface/platform.dart'; -export 'src/testing/fake_platform.dart'; diff --git a/packages/platform/lib/src/interface/local_platform.dart b/packages/platform/lib/src/interface/local_platform.dart deleted file mode 100644 index c24c01be55e..00000000000 --- a/packages/platform/lib/src/interface/local_platform.dart +++ /dev/null @@ -1,58 +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 'dart:io' as io show Platform, stdin, stdout; - -import 'platform.dart'; - -/// `Platform` implementation that delegates directly to `dart:io`. -class LocalPlatform extends Platform { - /// Creates a new [LocalPlatform]. - const LocalPlatform(); - - @override - int get numberOfProcessors => io.Platform.numberOfProcessors; - - @override - String get pathSeparator => io.Platform.pathSeparator; - - @override - String get operatingSystem => io.Platform.operatingSystem; - - @override - String get operatingSystemVersion => io.Platform.operatingSystemVersion; - - @override - String get localHostname => io.Platform.localHostname; - - @override - Map get environment => io.Platform.environment; - - @override - String get executable => io.Platform.executable; - - @override - String get resolvedExecutable => io.Platform.resolvedExecutable; - - @override - Uri get script => io.Platform.script; - - @override - List get executableArguments => io.Platform.executableArguments; - - @override - String? get packageConfig => io.Platform.packageConfig; - - @override - String get version => io.Platform.version; - - @override - bool get stdinSupportsAnsi => io.stdin.supportsAnsiEscapes; - - @override - bool get stdoutSupportsAnsi => io.stdout.supportsAnsiEscapes; - - @override - String get localeName => io.Platform.localeName; -} diff --git a/packages/platform/lib/src/interface/platform.dart b/packages/platform/lib/src/interface/platform.dart deleted file mode 100644 index dcc47be82a3..00000000000 --- a/packages/platform/lib/src/interface/platform.dart +++ /dev/null @@ -1,205 +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 'dart:convert'; - -/// Provides API parity with the `Platform` class in `dart:io`, but using -/// instance properties rather than static properties. This difference enables -/// the use of these APIs in tests, where you can provide mock implementations. -abstract class Platform { - /// Creates a new [Platform]. - const Platform(); - - /// A string constant to compare with [operatingSystem] to see if the platform - /// is Linux. - /// - /// Useful in case statements when switching on [operatingSystem]. - /// - /// To just check if the platform is Linux, use [isLinux]. - static const String linux = 'linux'; - - /// A string constant to compare with [operatingSystem] to see if the platform - /// is Windows. - /// - /// Useful in case statements when switching on [operatingSystem]. - /// - /// To just check if the platform is Windows, use [isWindows]. - static const String windows = 'windows'; - - /// A string constant to compare with [operatingSystem] to see if the platform - /// is macOS. - /// - /// Useful in case statements when switching on [operatingSystem]. - /// - /// To just check if the platform is macOS, use [isMacOS]. - static const String macOS = 'macos'; - - /// A string constant to compare with [operatingSystem] to see if the platform - /// is Android. - /// - /// Useful in case statements when switching on [operatingSystem]. - /// - /// To just check if the platform is Android, use [isAndroid]. - static const String android = 'android'; - - /// A string constant to compare with [operatingSystem] to see if the platform - /// is iOS. - /// - /// Useful in case statements when switching on [operatingSystem]. - /// - /// To just check if the platform is iOS, use [isIOS]. - static const String iOS = 'ios'; - - /// A string constant to compare with [operatingSystem] to see if the platform - /// is Fuchsia. - /// - /// Useful in case statements when switching on [operatingSystem]. - /// - /// To just check if the platform is Fuchsia, use [isFuchsia]. - static const String fuchsia = 'fuchsia'; - - /// A list of the possible values that [operatingSystem] can return. - static const List operatingSystemValues = [ - linux, - macOS, - windows, - android, - iOS, - fuchsia, - ]; - - /// The number of processors of the machine. - int get numberOfProcessors; - - /// The path separator used by the operating system to separate - /// components in file paths. - String get pathSeparator; - - /// A string (`linux`, `macos`, `windows`, `android`, `ios`, or `fuchsia`) - /// representing the operating system. - /// - /// The possible return values are available from [operatingSystemValues], and - /// there are constants for each of the platforms to use in switch statements - /// or conditionals (See [linux], [macOS], [windows], [android], [iOS], and - /// [fuchsia]). - String get operatingSystem; - - /// A string representing the version of the operating system or platform. - String get operatingSystemVersion; - - /// Get the local hostname for the system. - String get localHostname; - - /// True if the operating system is Linux. - bool get isLinux => operatingSystem == linux; - - /// True if the operating system is OS X. - bool get isMacOS => operatingSystem == macOS; - - /// True if the operating system is Windows. - bool get isWindows => operatingSystem == windows; - - /// True if the operating system is Android. - bool get isAndroid => operatingSystem == android; - - /// True if the operating system is iOS. - bool get isIOS => operatingSystem == iOS; - - /// True if the operating system is Fuchsia - bool get isFuchsia => operatingSystem == fuchsia; - - /// The environment for this process. - /// - /// The returned environment is an unmodifiable map whose content is - /// retrieved from the operating system on its first use. - /// - /// Environment variables on Windows are case-insensitive. The map - /// returned on Windows is therefore case-insensitive and will convert - /// all keys to upper case. On other platforms the returned map is - /// a standard case-sensitive map. - Map get environment; - - /// The path of the executable used to run the script in this isolate. - /// - /// The path returned is the literal path used to run the script. This - /// path might be relative or just be a name from which the executable - /// was found by searching the `PATH`. - /// - /// To get the absolute path to the resolved executable use - /// [resolvedExecutable]. - String get executable; - - /// The path of the executable used to run the script in this - /// isolate after it has been resolved by the OS. - /// - /// This is the absolute path, with all symlinks resolved, to the - /// executable used to run the script. - String get resolvedExecutable; - - /// The absolute URI of the script being run in this - /// isolate. - /// - /// If the script argument on the command line is relative, - /// it is resolved to an absolute URI before fetching the script, and - /// this absolute URI is returned. - /// - /// URI resolution only does string manipulation on the script path, and this - /// may be different from the file system's path resolution behavior. For - /// example, a symbolic link immediately followed by '..' will not be - /// looked up. - /// - /// If the executable environment does not support [script] an empty - /// [Uri] is returned. - Uri get script; - - /// The flags passed to the executable used to run the script in this - /// isolate. These are the command-line flags between the executable name - /// and the script name. Each fetch of `executableArguments` returns a new - /// list containing the flags passed to the executable. - List get executableArguments; - - /// The value of the `--packages` flag passed to the executable - /// used to run the script in this isolate. This is the configuration which - /// specifies how Dart packages are looked up. - /// - /// If there is no `--packages` flag, `null` is returned. - String? get packageConfig; - - /// The version of the current Dart runtime. - /// - /// The returned `String` is formatted as the [semver](http://semver.org) - /// version string of the current dart runtime, possibly followed by - /// whitespace and other version and build details. - String get version; - - /// When stdin is connected to a terminal, whether ANSI codes are supported. - bool get stdinSupportsAnsi; - - /// When stdout is connected to a terminal, whether ANSI codes are supported. - bool get stdoutSupportsAnsi; - - /// Get the name of the current locale. - String get localeName; - - /// Returns a JSON-encoded representation of this platform. - String toJson() { - return const JsonEncoder.withIndent(' ').convert({ - 'numberOfProcessors': numberOfProcessors, - 'pathSeparator': pathSeparator, - 'operatingSystem': operatingSystem, - 'operatingSystemVersion': operatingSystemVersion, - 'localHostname': localHostname, - 'environment': environment, - 'executable': executable, - 'resolvedExecutable': resolvedExecutable, - 'script': script.toString(), - 'executableArguments': executableArguments, - 'packageConfig': packageConfig, - 'version': version, - 'stdinSupportsAnsi': stdinSupportsAnsi, - 'stdoutSupportsAnsi': stdoutSupportsAnsi, - 'localeName': localeName, - }); - } -} diff --git a/packages/platform/lib/src/testing/fake_platform.dart b/packages/platform/lib/src/testing/fake_platform.dart deleted file mode 100644 index 43743d6ef13..00000000000 --- a/packages/platform/lib/src/testing/fake_platform.dart +++ /dev/null @@ -1,199 +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 'dart:convert'; - -import '../interface/platform.dart'; - -/// Provides a mutable implementation of the [Platform] interface. -class FakePlatform extends Platform { - /// Creates a new [FakePlatform] with the specified properties. - /// - /// Unspecified properties will *not* be assigned default values (they will - /// remain `null`). If an unset non-null value is read, a [StateError] will - /// be thrown instead of returning `null`. - FakePlatform({ - int? numberOfProcessors, - String? pathSeparator, - String? operatingSystem, - String? operatingSystemVersion, - String? localHostname, - Map? environment, - String? executable, - String? resolvedExecutable, - Uri? script, - List? executableArguments, - this.packageConfig, - String? version, - bool? stdinSupportsAnsi, - bool? stdoutSupportsAnsi, - String? localeName, - }) : _numberOfProcessors = numberOfProcessors, - _pathSeparator = pathSeparator, - _operatingSystem = operatingSystem, - _operatingSystemVersion = operatingSystemVersion, - _localHostname = localHostname, - _environment = environment, - _executable = executable, - _resolvedExecutable = resolvedExecutable, - _script = script, - _executableArguments = executableArguments, - _version = version, - _stdinSupportsAnsi = stdinSupportsAnsi, - _stdoutSupportsAnsi = stdoutSupportsAnsi, - _localeName = localeName; - - /// Creates a new [FakePlatform] with properties whose initial values mirror - /// the specified [platform]. - FakePlatform.fromPlatform(Platform platform) - : _numberOfProcessors = platform.numberOfProcessors, - _pathSeparator = platform.pathSeparator, - _operatingSystem = platform.operatingSystem, - _operatingSystemVersion = platform.operatingSystemVersion, - _localHostname = platform.localHostname, - _environment = Map.from(platform.environment), - _executable = platform.executable, - _resolvedExecutable = platform.resolvedExecutable, - _script = platform.script, - _executableArguments = List.from(platform.executableArguments), - packageConfig = platform.packageConfig, - _version = platform.version, - _stdinSupportsAnsi = platform.stdinSupportsAnsi, - _stdoutSupportsAnsi = platform.stdoutSupportsAnsi, - _localeName = platform.localeName; - - /// Creates a new [FakePlatform] with properties extracted from the encoded - /// JSON string. - /// - /// [json] must be a JSON string that matches the encoding produced by - /// [toJson]. - factory FakePlatform.fromJson(String json) { - final Map map = - const JsonDecoder().convert(json) as Map; - return FakePlatform( - numberOfProcessors: map['numberOfProcessors'] as int?, - pathSeparator: map['pathSeparator'] as String?, - operatingSystem: map['operatingSystem'] as String?, - operatingSystemVersion: map['operatingSystemVersion'] as String?, - localHostname: map['localHostname'] as String?, - environment: - (map['environment'] as Map).cast(), - executable: map['executable'] as String?, - resolvedExecutable: map['resolvedExecutable'] as String?, - script: Uri.parse(map['script'] as String), - executableArguments: - (map['executableArguments'] as List).cast(), - packageConfig: map['packageConfig'] as String?, - version: map['version'] as String?, - stdinSupportsAnsi: map['stdinSupportsAnsi'] as bool?, - stdoutSupportsAnsi: map['stdoutSupportsAnsi'] as bool?, - localeName: map['localeName'] as String?, - ); - } - - /// Creates a new [FakePlatform] from this one, with some properties replaced by the given properties. - FakePlatform copyWith({ - int? numberOfProcessors, - String? pathSeparator, - String? operatingSystem, - String? operatingSystemVersion, - String? localHostname, - Map? environment, - String? executable, - String? resolvedExecutable, - Uri? script, - List? executableArguments, - String? packageConfig, - String? version, - bool? stdinSupportsAnsi, - bool? stdoutSupportsAnsi, - String? localeName, - }) { - return FakePlatform( - numberOfProcessors: numberOfProcessors ?? this.numberOfProcessors, - pathSeparator: pathSeparator ?? this.pathSeparator, - operatingSystem: operatingSystem ?? this.operatingSystem, - operatingSystemVersion: - operatingSystemVersion ?? this.operatingSystemVersion, - localHostname: localHostname ?? this.localHostname, - environment: environment ?? this.environment, - executable: executable ?? this.executable, - resolvedExecutable: resolvedExecutable ?? this.resolvedExecutable, - script: script ?? this.script, - executableArguments: executableArguments ?? this.executableArguments, - packageConfig: packageConfig ?? this.packageConfig, - version: version ?? this.version, - stdinSupportsAnsi: stdinSupportsAnsi ?? this.stdinSupportsAnsi, - stdoutSupportsAnsi: stdoutSupportsAnsi ?? this.stdoutSupportsAnsi, - localeName: localeName ?? this.localeName, - ); - } - - @override - int get numberOfProcessors => _throwIfNull(_numberOfProcessors); - int? _numberOfProcessors; - - @override - String get pathSeparator => _throwIfNull(_pathSeparator); - String? _pathSeparator; - - @override - String get operatingSystem => _throwIfNull(_operatingSystem); - String? _operatingSystem; - - @override - String get operatingSystemVersion => _throwIfNull(_operatingSystemVersion); - String? _operatingSystemVersion; - - @override - String get localHostname => _throwIfNull(_localHostname); - String? _localHostname; - - @override - Map get environment => _throwIfNull(_environment); - Map? _environment; - - @override - String get executable => _throwIfNull(_executable); - String? _executable; - - @override - String get resolvedExecutable => _throwIfNull(_resolvedExecutable); - String? _resolvedExecutable; - - @override - Uri get script => _throwIfNull(_script); - Uri? _script; - - @override - List get executableArguments => _throwIfNull(_executableArguments); - List? _executableArguments; - - @override - String? packageConfig; - - @override - String get version => _throwIfNull(_version); - String? _version; - - @override - bool get stdinSupportsAnsi => _throwIfNull(_stdinSupportsAnsi); - bool? _stdinSupportsAnsi; - - @override - bool get stdoutSupportsAnsi => _throwIfNull(_stdoutSupportsAnsi); - bool? _stdoutSupportsAnsi; - - @override - String get localeName => _throwIfNull(_localeName); - String? _localeName; - - T _throwIfNull(T? value) { - if (value == null) { - throw StateError( - 'Tried to read property of FakePlatform but it was unset.'); - } - return value; - } -} diff --git a/packages/platform/pubspec.yaml b/packages/platform/pubspec.yaml deleted file mode 100644 index 739f9921913..00000000000 --- a/packages/platform/pubspec.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: platform -description: A pluggable, mockable platform information abstraction for Dart. -repository: https://github.com/flutter/packages/tree/main/packages/platform -issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+platform%22 -version: 3.1.4 - -environment: - sdk: ^3.1.0 - -dev_dependencies: - test: ^1.16.8 - -topics: - - information - - platform diff --git a/packages/platform/test/fake_platform_test.dart b/packages/platform/test/fake_platform_test.dart deleted file mode 100644 index 08795ce9852..00000000000 --- a/packages/platform/test/fake_platform_test.dart +++ /dev/null @@ -1,165 +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 'dart:io' as io; - -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -void _expectPlatformsEqual(Platform actual, Platform expected) { - expect(actual.numberOfProcessors, expected.numberOfProcessors); - expect(actual.pathSeparator, expected.pathSeparator); - expect(actual.operatingSystem, expected.operatingSystem); - expect(actual.operatingSystemVersion, expected.operatingSystemVersion); - expect(actual.localHostname, expected.localHostname); - expect(actual.environment, expected.environment); - expect(actual.executable, expected.executable); - expect(actual.resolvedExecutable, expected.resolvedExecutable); - expect(actual.script, expected.script); - expect(actual.executableArguments, expected.executableArguments); - expect(actual.packageConfig, expected.packageConfig); - expect(actual.version, expected.version); - expect(actual.localeName, expected.localeName); -} - -void main() { - group('FakePlatform', () { - late FakePlatform fake; - late LocalPlatform local; - - setUp(() { - fake = FakePlatform(); - local = const LocalPlatform(); - }); - - group('fromPlatform', () { - setUp(() { - fake = FakePlatform.fromPlatform(local); - }); - - test('copiesAllProperties', () { - _expectPlatformsEqual(fake, local); - }); - - test('convertsPropertiesToMutable', () { - final String key = fake.environment.keys.first; - - expect(fake.environment[key], local.environment[key]); - fake.environment[key] = 'FAKE'; - expect(fake.environment[key], 'FAKE'); - - expect( - fake.executableArguments.length, local.executableArguments.length); - fake.executableArguments.add('ARG'); - expect(fake.executableArguments.last, 'ARG'); - }); - }); - - group('copyWith', () { - setUp(() { - fake = FakePlatform.fromPlatform(local); - }); - - test('overrides a value, but leaves others intact', () { - final FakePlatform copy = fake.copyWith( - numberOfProcessors: -1, - ); - expect(copy.numberOfProcessors, equals(-1)); - expect(copy.pathSeparator, local.pathSeparator); - expect(copy.operatingSystem, local.operatingSystem); - expect(copy.operatingSystemVersion, local.operatingSystemVersion); - expect(copy.localHostname, local.localHostname); - expect(copy.environment, local.environment); - expect(copy.executable, local.executable); - expect(copy.resolvedExecutable, local.resolvedExecutable); - expect(copy.script, local.script); - expect(copy.executableArguments, local.executableArguments); - expect(copy.packageConfig, local.packageConfig); - expect(copy.version, local.version); - expect(copy.localeName, local.localeName); - }); - test('can override all values', () { - fake = FakePlatform( - numberOfProcessors: 8, - pathSeparator: ':', - operatingSystem: 'fake', - operatingSystemVersion: '0.1.0', - localHostname: 'host', - environment: {'PATH': '.'}, - executable: 'executable', - resolvedExecutable: '/executable', - script: Uri.file('/platform/test/fake_platform_test.dart'), - executableArguments: ['scriptarg'], - version: '0.1.1', - stdinSupportsAnsi: false, - stdoutSupportsAnsi: true, - localeName: 'local', - ); - final FakePlatform copy = fake.copyWith( - numberOfProcessors: local.numberOfProcessors, - pathSeparator: local.pathSeparator, - operatingSystem: local.operatingSystem, - operatingSystemVersion: local.operatingSystemVersion, - localHostname: local.localHostname, - environment: local.environment, - executable: local.executable, - resolvedExecutable: local.resolvedExecutable, - script: local.script, - executableArguments: local.executableArguments, - packageConfig: local.packageConfig, - version: local.version, - stdinSupportsAnsi: local.stdinSupportsAnsi, - stdoutSupportsAnsi: local.stdoutSupportsAnsi, - localeName: local.localeName, - ); - _expectPlatformsEqual(copy, local); - }); - }); - - group('json', () { - test('fromJson', () { - final String json = io.File('test/platform.json').readAsStringSync(); - fake = FakePlatform.fromJson(json); - expect(fake.numberOfProcessors, 8); - expect(fake.pathSeparator, '/'); - expect(fake.operatingSystem, 'macos'); - expect(fake.operatingSystemVersion, '10.14.5'); - expect(fake.localHostname, 'platform.test.org'); - expect(fake.environment, { - 'PATH': '/bin', - 'PWD': '/platform', - }); - expect(fake.executable, '/bin/dart'); - expect(fake.resolvedExecutable, '/bin/dart'); - expect(fake.script, Uri.file('/platform/test/fake_platform_test.dart')); - expect(fake.executableArguments, ['--checked']); - expect(fake.packageConfig, null); - expect(fake.version, '1.22.0'); - expect(fake.localeName, 'de/de'); - }); - - test('fromJsonToJson', () { - fake = FakePlatform.fromJson(local.toJson()); - _expectPlatformsEqual(fake, local); - }); - }); - }); - - test('Throws when unset non-null values are read', () { - final FakePlatform platform = FakePlatform(); - - expect(() => platform.numberOfProcessors, throwsA(isStateError)); - expect(() => platform.pathSeparator, throwsA(isStateError)); - expect(() => platform.operatingSystem, throwsA(isStateError)); - expect(() => platform.operatingSystemVersion, throwsA(isStateError)); - expect(() => platform.localHostname, throwsA(isStateError)); - expect(() => platform.environment, throwsA(isStateError)); - expect(() => platform.executable, throwsA(isStateError)); - expect(() => platform.resolvedExecutable, throwsA(isStateError)); - expect(() => platform.script, throwsA(isStateError)); - expect(() => platform.executableArguments, throwsA(isStateError)); - expect(() => platform.version, throwsA(isStateError)); - expect(() => platform.localeName, throwsA(isStateError)); - }); -} diff --git a/packages/platform/test/platform.json b/packages/platform/test/platform.json deleted file mode 100644 index 60b7c139d8a..00000000000 --- a/packages/platform/test/platform.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "numberOfProcessors": 8, - "pathSeparator": "/", - "operatingSystem": "macos", - "operatingSystemVersion": "10.14.5", - "localHostname": "platform.test.org", - "environment": { - "PATH": "/bin", - "PWD": "/platform" - }, - "executable": "/bin/dart", - "resolvedExecutable": "/bin/dart", - "script": "file:///platform/test/fake_platform_test.dart", - "executableArguments": [ - "--checked" - ], - "packageConfig": null, - "version": "1.22.0", - "localeName": "de/de" -} \ No newline at end of file diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md index 6f3c3ab59c1..7f9f9865049 100644 --- a/packages/plugin_platform_interface/CHANGELOG.md +++ b/packages/plugin_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.1.8 diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml index d3d7aeadb63..0fee03aab35 100644 --- a/packages/plugin_platform_interface/pubspec.yaml +++ b/packages/plugin_platform_interface/pubspec.yaml @@ -18,7 +18,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.1.8 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: meta: ^1.3.0 diff --git a/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md index a822866f910..0a47b7de02e 100644 --- a/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md @@ -1,8 +1,13 @@ ## NEXT +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 0.10.1+1 + * Updates support matrix in README to indicate that iOS 11 is no longer supported. * Clients on versions of Flutter that still support iOS 11 can continue to use this package with iOS 11, but will not receive any further updates to the iOS implementation. +* Removes invalid `implements` tag in pubspec. ## 0.10.1 diff --git a/packages/pointer_interceptor/pointer_interceptor/example/README.md b/packages/pointer_interceptor/pointer_interceptor/example/README.md index 9fddf8c0a09..d2d61170582 100644 --- a/packages/pointer_interceptor/pointer_interceptor/example/README.md +++ b/packages/pointer_interceptor/pointer_interceptor/example/README.md @@ -14,4 +14,4 @@ The command above will run the integration tests for this package. Make sure that you have `chromedriver` running in port `4444`. -Read more on: [flutter.dev > Docs > Testing & debugging > Integration testing](https://flutter.dev/docs/testing/integration-tests). +Read more on: [flutter.dev > Docs > Testing & debugging > Integration testing](https://docs.flutter.dev/testing/integration-tests). diff --git a/packages/pointer_interceptor/pointer_interceptor/example/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor/example/pubspec.yaml index 9f8b29e288b..7c6591908ef 100644 --- a/packages/pointer_interceptor/pointer_interceptor/example/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml index 5846d73014f..3e10acce31b 100644 --- a/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml @@ -2,15 +2,14 @@ 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/pointer_interceptor issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pointer_interceptor%22 -version: 0.10.1 +version: 0.10.1+1 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: - implements: pointer_interceptor_platform_interface platforms: web: default_package: pointer_interceptor_web diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor_ios/CHANGELOG.md index cd4057d6a91..3d0c2bad8c2 100644 --- a/packages/pointer_interceptor/pointer_interceptor_ios/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.10.1 +* Adds Swift Package Manager compatibility. * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. ## 0.10.0+2 diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/README.md b/packages/pointer_interceptor/pointer_interceptor_ios/README.md index ca30d6d9def..9d0807c190d 100644 --- a/packages/pointer_interceptor/pointer_interceptor_ios/README.md +++ b/packages/pointer_interceptor/pointer_interceptor_ios/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/pointer_interceptor -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin \ No newline at end of file +[2]: https://flutter.dev/to/endorsed-federated-plugin \ No newline at end of file diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/Assets/.gitkeep b/packages/pointer_interceptor/pointer_interceptor_ios/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios.podspec b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios.podspec index 8a106eec53d..e89981a7069 100644 --- a/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios.podspec +++ b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios.podspec @@ -13,7 +13,7 @@ This Flutter plugin provides means to prevent gestures from being swallowed by P s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/pointer_interceptor/pointer_interceptor_ios' } - s.source_files = 'Classes/**/*' + s.source_files = 'pointer_interceptor_ios/Sources/pointer_interceptor_ios/**/*.swift' s.dependency 'Flutter' s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. @@ -23,5 +23,5 @@ This Flutter plugin provides means to prevent gestures from being swallowed by P 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } - s.resource_bundles = {'pointer_interceptor_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'pointer_interceptor_ios_privacy' => ['pointer_interceptor_ios/Sources/pointer_interceptor_ios/PrivacyInfo.xcprivacy']} end diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Package.swift b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Package.swift new file mode 100644 index 00000000000..a122e735285 --- /dev/null +++ b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "pointer_interceptor_ios", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "pointer-interceptor-ios", targets: ["pointer_interceptor_ios"]) + ], + dependencies: [], + targets: [ + .target( + name: "pointer_interceptor_ios", + dependencies: [], + resources: [ + .process("PrivacyInfo.xcprivacy") + ] + ) + ] +) diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/Classes/PointerInterceptorFactory.swift b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PointerInterceptorFactory.swift similarity index 100% rename from packages/pointer_interceptor/pointer_interceptor_ios/ios/Classes/PointerInterceptorFactory.swift rename to packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PointerInterceptorFactory.swift diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/Classes/PointerInterceptorIosPlugin.swift b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PointerInterceptorIosPlugin.swift similarity index 100% rename from packages/pointer_interceptor/pointer_interceptor_ios/ios/Classes/PointerInterceptorIosPlugin.swift rename to packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PointerInterceptorIosPlugin.swift diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/Classes/PointerInterceptorView.swift b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PointerInterceptorView.swift similarity index 100% rename from packages/pointer_interceptor/pointer_interceptor_ios/ios/Classes/PointerInterceptorView.swift rename to packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PointerInterceptorView.swift diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/ios/Resources/PrivacyInfo.xcprivacy b/packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PrivacyInfo.xcprivacy similarity index 100% rename from packages/pointer_interceptor/pointer_interceptor_ios/ios/Resources/PrivacyInfo.xcprivacy rename to packages/pointer_interceptor/pointer_interceptor_ios/ios/pointer_interceptor_ios/Sources/pointer_interceptor_ios/PrivacyInfo.xcprivacy diff --git a/packages/pointer_interceptor/pointer_interceptor_ios/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor_ios/pubspec.yaml index df2d338d4cb..d9d43793916 100644 --- a/packages/pointer_interceptor/pointer_interceptor_ios/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: pointer_interceptor_ios description: iOS implementation of the pointer_interceptor plugin. repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor/pointer_interceptor_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apointer_interceptor -version: 0.10.0+2 +version: 0.10.1 environment: sdk: ^3.2.3 diff --git a/packages/pointer_interceptor/pointer_interceptor_platform_interface/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor_platform_interface/CHANGELOG.md index 298c3413c6f..1169176c40f 100644 --- a/packages/pointer_interceptor/pointer_interceptor_platform_interface/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 0.10.0+1 * Updates minimum required plugin_platform_interface version to 2.1.7. diff --git a/packages/pointer_interceptor/pointer_interceptor_platform_interface/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor_platform_interface/pubspec.yaml index 5bdd4b227ce..c66c18ab9da 100644 --- a/packages/pointer_interceptor/pointer_interceptor_platform_interface/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor_platform_interface/pubspec.yaml @@ -6,8 +6,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.10.0+1 environment: - sdk: '>=3.1.0 <4.0.0' - flutter: '>=3.13.0' + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/pointer_interceptor/pointer_interceptor_web/README.md b/packages/pointer_interceptor/pointer_interceptor_web/README.md index 45db9bdcbc7..063c9236a3e 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/README.md +++ b/packages/pointer_interceptor/pointer_interceptor_web/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/pointer_interceptor -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin \ No newline at end of file +[2]: https://flutter.dev/to/endorsed-federated-plugin \ No newline at end of file diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart b/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart index 35fe738ecfe..f1b323ae316 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart @@ -64,68 +64,6 @@ void main() { expect(element.id, 'background-html-view'); }, semanticsEnabled: false); }); - - group('With semantics', () { - testWidgets('finds semantics of wrapped widgets', - (WidgetTester tester) async { - await _fullyRenderApp(tester); - - final web.Element element = - _getHtmlElementAtCenter(clickableButtonFinder, tester); - - expect(element.tagName.toLowerCase(), 'flt-semantics'); - expect(element.getAttribute('aria-label'), 'Works As Expected'); - }, - // TODO(bparrishMines): The semantics label is returning null. - // See https://github.com/flutter/flutter/issues/145238 - skip: true); - - testWidgets( - 'finds semantics of wrapped widgets with intercepting set to false', - (WidgetTester tester) async { - await _fullyRenderApp(tester); - - final web.Element element = - _getHtmlElementAtCenter(clickableWrappedButtonFinder, tester); - - expect(element.tagName.toLowerCase(), 'flt-semantics'); - expect(element.getAttribute('aria-label'), - 'Never calls onPressed transparent'); - }, - // TODO(bparrishMines): The semantics label is returning null. - // See https://github.com/flutter/flutter/issues/145238 - skip: true); - - testWidgets('finds semantics of unwrapped elements', - (WidgetTester tester) async { - await _fullyRenderApp(tester); - - final web.Element element = - _getHtmlElementAtCenter(nonClickableButtonFinder, tester); - - expect(element.tagName.toLowerCase(), 'flt-semantics'); - expect(element.getAttribute('aria-label'), 'Never calls onPressed'); - }, - // TODO(bparrishMines): The semantics label is returning null. - // See https://github.com/flutter/flutter/issues/145238 - skip: true); - - // Notice that, when hit-testing the background platform view, instead of - // finding a semantics node, the platform view itself is found. This is - // because the platform view does not add interactive semantics nodes into - // the framework's semantics tree. Instead, its semantics is determined by - // the HTML content of the platform view itself. Flutter's semantics tree - // simply allows the hit test to land on the platform view by making itself - // hit test transparent. - testWidgets('on background directly', (WidgetTester tester) async { - await _fullyRenderApp(tester); - - final web.Element element = - _getHtmlElementAt(tester.getTopLeft(backgroundFinder)); - - expect(element.id, 'background-html-view'); - }); - }); } Future _fullyRenderApp(WidgetTester tester) async { diff --git a/packages/process/CHANGELOG.md b/packages/process/CHANGELOG.md index 7a7480a1c7b..f2b826c4b7b 100644 --- a/packages/process/CHANGELOG.md +++ b/packages/process/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 5.0.2 diff --git a/packages/process/pubspec.yaml b/packages/process/pubspec.yaml index 394978f5697..b00f5870dd3 100644 --- a/packages/process/pubspec.yaml +++ b/packages/process/pubspec.yaml @@ -5,7 +5,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 5.0.2 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: file: '>=6.0.0 <8.0.0' diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md index e23adf50b27..c9b4308251b 100644 --- a/packages/quick_actions/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/quick_actions/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 1.0.7 diff --git a/packages/quick_actions/quick_actions/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/quick_actions/quick_actions/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/quick_actions/quick_actions/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/quick_actions/quick_actions/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/quick_actions/quick_actions/example/android/app/src/main/AndroidManifest.xml b/packages/quick_actions/quick_actions/example/android/app/src/main/AndroidManifest.xml index 4f384b7c6b1..20969735d86 100644 --- a/packages/quick_actions/quick_actions/example/android/app/src/main/AndroidManifest.xml +++ b/packages/quick_actions/quick_actions/example/android/app/src/main/AndroidManifest.xml @@ -4,9 +4,6 @@ - project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/quick_actions/quick_actions/example/pubspec.yaml b/packages/quick_actions/quick_actions/example/pubspec.yaml index 14770698028..e6ade3d47da 100644 --- a/packages/quick_actions/quick_actions/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the quick_actions plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index e58d223c3ab..cf76b5061c5 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -6,8 +6,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.7 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index e8d488aa3a7..5b155e4b82c 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,16 @@ +## 1.0.14 + +* Updates AGP version to 8.4.1. + +## 1.0.13 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 1.0.12 + +* Switches from using `ShortcutManager` to `ShortcutManagerCompat`. + ## 1.0.11 * Updates minSdkVersion to 19. diff --git a/packages/quick_actions/quick_actions_android/README.md b/packages/quick_actions/quick_actions_android/README.md index e42c6a6e39b..cb4a8e08826 100644 --- a/packages/quick_actions/quick_actions_android/README.md +++ b/packages/quick_actions/quick_actions_android/README.md @@ -16,5 +16,5 @@ should add it to your `pubspec.yaml` as usual. If you would like to contribute to the plugin, check out our [contribution guide][3]. [1]: https://pub.dev/packages/quick_actions -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md diff --git a/packages/quick_actions/quick_actions_android/android/build.gradle b/packages/quick_actions/quick_actions_android/android/build.gradle index 7398e660a17..d791101025e 100644 --- a/packages/quick_actions/quick_actions_android/android/build.gradle +++ b/packages/quick_actions/quick_actions_android/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:8.4.1' } } diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java index 1549cf143b0..1c124ae3515 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActions.java @@ -8,16 +8,16 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; import android.content.res.Resources; -import android.graphics.drawable.Icon; import android.os.Build; import android.os.Handler; import android.os.Looper; import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; import io.flutter.plugins.quickactions.Messages.AndroidQuickActionsApi; import io.flutter.plugins.quickactions.Messages.FlutterError; import io.flutter.plugins.quickactions.Messages.Result; @@ -61,9 +61,7 @@ public void setShortcutItems( result.success(null); return; } - ShortcutManager shortcutManager = - (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); - List shortcuts = shortcutItemMessageToShortcutInfo(itemsList); + List shortcuts = shortcutItemMessageToShortcutInfo(itemsList); Executor uiThreadExecutor = new UiThreadExecutor(); ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); @@ -72,7 +70,7 @@ public void setShortcutItems( () -> { boolean dynamicShortcutsSet = false; try { - shortcutManager.setDynamicShortcuts(shortcuts); + ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts); dynamicShortcutsSet = true; } catch (Exception e) { // Leave dynamicShortcutsSet as false @@ -101,9 +99,7 @@ public void clearShortcutItems() { if (!isVersionAllowed()) { return; } - ShortcutManager shortcutManager = - (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); - shortcutManager.removeAllDynamicShortcuts(); + ShortcutManagerCompat.removeAllDynamicShortcuts(context); } @Override @@ -111,8 +107,6 @@ public void clearShortcutItems() { if (!isVersionAllowed()) { return null; } - ShortcutManager shortcutManager = - (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); if (activity == null) { throw new FlutterError( "quick_action_getlaunchaction_no_activity", @@ -122,31 +116,32 @@ public void clearShortcutItems() { final Intent intent = activity.getIntent(); final String launchAction = intent.getStringExtra(EXTRA_ACTION); if (launchAction != null && !launchAction.isEmpty()) { - shortcutManager.reportShortcutUsed(launchAction); + ShortcutManagerCompat.reportShortcutUsed(context, launchAction); intent.removeExtra(EXTRA_ACTION); } return launchAction; } @TargetApi(Build.VERSION_CODES.N_MR1) - private List shortcutItemMessageToShortcutInfo( + private List shortcutItemMessageToShortcutInfo( @NonNull List shortcuts) { - final List shortcutInfos = new ArrayList<>(); + final List shortcutInfos = new ArrayList<>(); for (ShortcutItemMessage shortcut : shortcuts) { final String icon = shortcut.getIcon(); final String type = shortcut.getType(); final String title = shortcut.getLocalizedTitle(); - final ShortcutInfo.Builder shortcutBuilder = new ShortcutInfo.Builder(context, type); + final ShortcutInfoCompat.Builder shortcutBuilder = + new ShortcutInfoCompat.Builder(context, type); final int resourceId = loadResourceId(context, icon); final Intent intent = getIntentToOpenMainActivity(type); if (resourceId > 0) { - shortcutBuilder.setIcon(Icon.createWithResource(context, resourceId)); + shortcutBuilder.setIcon(IconCompat.createWithResource(context, resourceId)); } - final ShortcutInfo shortcutInfo = + final ShortcutInfoCompat shortcutInfo = shortcutBuilder.setLongLabel(title).setShortLabel(title).setIntent(intent).build(); shortcutInfos.add(shortcutInfo); } diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java index fd0184302ab..9dd701054ad 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java @@ -7,12 +7,12 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.pm.ShortcutManager; import android.os.Build; import android.util.Log; import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.core.content.pm.ShortcutManagerCompat; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -43,19 +43,6 @@ public QuickActionsPlugin() { this.sdkChecker = capabilityChecker; } - /** - * Plugin registration. - * - *

Must be called when the application is created. - */ - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - QuickActions quickActions = new QuickActions(registrar.context()); - quickActions.setActivity(registrar.activity()); - Messages.AndroidQuickActionsApi.setup(registrar.messenger(), quickActions); - } - @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { this.quickActions = new QuickActions(binding.getApplicationContext()); @@ -108,15 +95,13 @@ public boolean onNewIntent(@NonNull Intent intent) { // Notify the Dart side if the launch intent has the intent extra relevant to quick actions. if (intent.hasExtra(QuickActions.EXTRA_ACTION) && activity != null) { Context context = activity.getApplicationContext(); - ShortcutManager shortcutManager = - (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); String shortcutId = intent.getStringExtra(QuickActions.EXTRA_ACTION); quickActionsFlutterApi.launchAction( shortcutId, value -> { // noop }); - shortcutManager.reportShortcutUsed(shortcutId); + ShortcutManagerCompat.reportShortcutUsed(context, shortcutId); } return false; } diff --git a/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java index 6c2e963a80c..7d060d81c14 100644 --- a/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java @@ -13,7 +13,6 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.pm.ShortcutManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; @@ -76,8 +75,6 @@ public void onAttachedToActivity_buildVersionSupported_invokesLaunchMethod() when(mockActivityPluginBinding.getActivity()).thenReturn(mockMainActivity); final Context mockContext = mock(Context.class); when(mockMainActivity.getApplicationContext()).thenReturn(mockContext); - final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); - when(mockContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mockShortcutManager); plugin.onAttachedToActivity(mockActivityPluginBinding); // Act @@ -118,8 +115,6 @@ public void onNewIntent_buildVersionSupported_invokesLaunchMethod() { when(mockActivityPluginBinding.getActivity()).thenReturn(mockMainActivity); final Context mockContext = mock(Context.class); when(mockMainActivity.getApplicationContext()).thenReturn(mockContext); - final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); - when(mockContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mockShortcutManager); plugin.onAttachedToActivity(mockActivityPluginBinding); // Act diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/main/AndroidManifest.xml b/packages/quick_actions/quick_actions_android/example/android/app/src/main/AndroidManifest.xml index 4f384b7c6b1..20969735d86 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/src/main/AndroidManifest.xml +++ b/packages/quick_actions/quick_actions_android/example/android/app/src/main/AndroidManifest.xml @@ -4,9 +4,6 @@ - project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/quick_actions/quick_actions_android/example/pubspec.yaml b/packages/quick_actions/quick_actions_android/example/pubspec.yaml index c393b89a874..632624f697f 100644 --- a/packages/quick_actions/quick_actions_android/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the quick_actions plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index 0fc6f1fe27b..9571e8bc3a8 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -2,11 +2,11 @@ name: quick_actions_android description: An implementation for the Android platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 1.0.11 +version: 1.0.14 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md index c7f6a3b983c..a8af40f1901 100644 --- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md @@ -1,5 +1,10 @@ -## NEXT +## 1.1.1 +* Updates to a newer version of Pigeon. + +## 1.1.0 + +* Adds Swift Package Manager compatibility. * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. ## 1.0.10 diff --git a/packages/quick_actions/quick_actions_ios/README.md b/packages/quick_actions/quick_actions_ios/README.md index e679fa01b4a..e81fc6a4de0 100644 --- a/packages/quick_actions/quick_actions_ios/README.md +++ b/packages/quick_actions/quick_actions_ios/README.md @@ -16,5 +16,5 @@ should add it to your `pubspec.yaml` as usual. If you would like to contribute to the plugin, check out our [contribution guide][3]. [1]: https://pub.dev/packages/quick_actions -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift index 85477415667..c1924113ba1 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import UIKit + @testable import quick_actions_ios final class MockShortcutItemProvider: ShortcutItemProviding { diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift index f0dff1650b7..bf60a191ca3 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -12,7 +12,7 @@ class MockFlutterApi: IOSQuickActionsFlutterApiProtocol { var launchActionCallback: ((String) -> Void)? = nil func launchAction( - action actionArg: String, completion: @escaping (Result) -> Void + action actionArg: String, completion: @escaping (Result) -> Void ) { self.launchActionCallback?(actionArg) completion(.success(Void())) diff --git a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec index 77082f66ec3..6bfb72f4231 100644 --- a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec +++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec @@ -15,7 +15,7 @@ Downloaded by pub (not CocoaPods). s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/quick_actions' } s.documentation_url = 'https://pub.dev/packages/quick_actions' s.swift_version = '5.0' - s.source_files = 'Classes/**/*.swift' + s.source_files = 'quick_actions_ios/Sources/quick_actions_ios/*.swift' s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', @@ -23,5 +23,5 @@ Downloaded by pub (not CocoaPods). s.dependency 'Flutter' s.platform = :ios, '12.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.resource_bundles = {'quick_actions_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'quick_actions_ios_privacy' => ['quick_actions_ios/Sources/quick_actions_ios/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Package.swift b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Package.swift new file mode 100644 index 00000000000..c02db8e9c26 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.9 + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import PackageDescription + +let package = Package( + name: "quick_actions_ios", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "quick-actions-ios", targets: ["quick_actions_ios"]) + ], + dependencies: [], + targets: [ + .target( + name: "quick_actions_ios", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/QuickActionsPlugin.swift similarity index 100% rename from packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift rename to packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/QuickActionsPlugin.swift diff --git a/packages/quick_actions/quick_actions_ios/ios/Resources/PrivacyInfo.xcprivacy b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/quick_actions/quick_actions_ios/ios/Resources/PrivacyInfo.xcprivacy rename to packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/Resources/PrivacyInfo.xcprivacy diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/ShortcutItemProviding.swift similarity index 100% rename from packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift rename to packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/ShortcutItemProviding.swift diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/messages.g.swift b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/messages.g.swift similarity index 56% rename from packages/quick_actions/quick_actions_ios/ios/Classes/messages.g.swift rename to packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/messages.g.swift index ed2b5828085..f4a4091c333 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/messages.g.swift +++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Sources/quick_actions_ios/messages.g.swift @@ -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 (v12.0.1), do not edit directly. +// Autogenerated from Pigeon (v20.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -14,10 +14,22 @@ import Foundation #error("Unsupported platform.") #endif -extension FlutterError: Error {} +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? -private func isNullish(_ value: Any?) -> Bool { - return value is NSNull || value == nil + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } } private func wrapResult(_ result: Any?) -> [Any?] { @@ -25,6 +37,13 @@ private func wrapResult(_ result: Any?) -> [Any?] { } private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } if let flutterError = error as? FlutterError { return [ flutterError.code, @@ -39,6 +58,16 @@ private func wrapError(_ error: Any) -> [Any?] { ] } +private func createConnectionError(withChannelName channelName: String) -> PigeonError { + return PigeonError( + code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", + details: "") +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + private func nilOrValue(_ value: Any?) -> T? { if value is NSNull { return nil } return value as! T? @@ -55,10 +84,11 @@ struct ShortcutItemMessage { /// Name of native resource to be displayed as the icon for this item. var icon: String? = nil - static func fromList(_ list: [Any?]) -> ShortcutItemMessage? { - let type = list[0] as! String - let localizedTitle = list[1] as! String - let icon: String? = nilOrValue(list[2]) + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> ShortcutItemMessage? { + let type = __pigeon_list[0] as! String + let localizedTitle = __pigeon_list[1] as! String + let icon: String? = nilOrValue(__pigeon_list[2]) return ShortcutItemMessage( type: type, @@ -74,10 +104,10 @@ struct ShortcutItemMessage { ] } } -private class IOSQuickActionsApiCodecReader: FlutterStandardReader { +private class messagesPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { - case 128: + case 129: return ShortcutItemMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -85,10 +115,10 @@ private class IOSQuickActionsApiCodecReader: FlutterStandardReader { } } -private class IOSQuickActionsApiCodecWriter: FlutterStandardWriter { +private class messagesPigeonCodecWriter: FlutterStandardWriter { override func writeValue(_ value: Any) { if let value = value as? ShortcutItemMessage { - super.writeByte(128) + super.writeByte(129) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -96,18 +126,18 @@ private class IOSQuickActionsApiCodecWriter: FlutterStandardWriter { } } -private class IOSQuickActionsApiCodecReaderWriter: FlutterStandardReaderWriter { +private class messagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { override func reader(with data: Data) -> FlutterStandardReader { - return IOSQuickActionsApiCodecReader(data: data) + return messagesPigeonCodecReader(data: data) } override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return IOSQuickActionsApiCodecWriter(data: data) + return messagesPigeonCodecWriter(data: data) } } -class IOSQuickActionsApiCodec: FlutterStandardMessageCodec { - static let shared = IOSQuickActionsApiCodec(readerWriter: IOSQuickActionsApiCodecReaderWriter()) +class messagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = messagesPigeonCodec(readerWriter: messagesPigeonCodecReaderWriter()) } /// Generated protocol from Pigeon that represents a handler of messages from Flutter. @@ -120,13 +150,17 @@ protocol IOSQuickActionsApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class IOSQuickActionsApiSetup { - /// The codec used by IOSQuickActionsApi. - static var codec: FlutterStandardMessageCodec { IOSQuickActionsApiCodec.shared } + static var codec: FlutterStandardMessageCodec { messagesPigeonCodec.shared } /// Sets up an instance of `IOSQuickActionsApi` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: IOSQuickActionsApi?) { + static func setUp( + binaryMessenger: FlutterBinaryMessenger, api: IOSQuickActionsApi?, + messageChannelSuffix: String = "" + ) { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" /// Sets the dynamic shortcuts for the app. let setShortcutItemsChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems", + name: + "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { setShortcutItemsChannel.setMessageHandler { message, reply in @@ -144,7 +178,8 @@ class IOSQuickActionsApiSetup { } /// Removes all dynamic shortcuts. let clearShortcutItemsChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems", + name: + "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { clearShortcutItemsChannel.setMessageHandler { _, reply in @@ -164,22 +199,39 @@ class IOSQuickActionsApiSetup { protocol IOSQuickActionsFlutterApiProtocol { /// Sends a string representing a shortcut from the native platform to the app. func launchAction( - action actionArg: String, completion: @escaping (Result) -> Void) + action actionArg: String, completion: @escaping (Result) -> Void) } class IOSQuickActionsFlutterApi: IOSQuickActionsFlutterApiProtocol { private let binaryMessenger: FlutterBinaryMessenger - init(binaryMessenger: FlutterBinaryMessenger) { + private let messageChannelSuffix: String + init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") { self.binaryMessenger = binaryMessenger + self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + } + var codec: messagesPigeonCodec { + return messagesPigeonCodec.shared } /// Sends a string representing a shortcut from the native platform to the app. func launchAction( - action actionArg: String, completion: @escaping (Result) -> Void + action actionArg: String, completion: @escaping (Result) -> Void ) { + let channelName: String = + "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction", - binaryMessenger: binaryMessenger) - channel.sendMessage([actionArg] as [Any?]) { _ in - completion(.success(Void())) + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([actionArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(Void())) + } } } } diff --git a/packages/quick_actions/quick_actions_ios/lib/messages.g.dart b/packages/quick_actions/quick_actions_ios/lib/messages.g.dart index f1d64464772..199871ff803 100644 --- a/packages/quick_actions/quick_actions_ios/lib/messages.g.dart +++ b/packages/quick_actions/quick_actions_ios/lib/messages.g.dart @@ -1,9 +1,9 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v12.0.1), do not edit directly. +// Autogenerated from Pigeon (v20.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 +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -11,6 +11,13 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + List wrapResponse( {Object? result, PlatformException? error, bool empty = false}) { if (empty) { @@ -57,12 +64,12 @@ class ShortcutItemMessage { } } -class _IOSQuickActionsApiCodec extends StandardMessageCodec { - const _IOSQuickActionsApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is ShortcutItemMessage) { - buffer.putUint8(128); + buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -72,7 +79,7 @@ class _IOSQuickActionsApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 129: return ShortcutItemMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -84,31 +91,36 @@ class IOSQuickActionsApi { /// Constructor for [IOSQuickActionsApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - IOSQuickActionsApi({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; + IOSQuickActionsApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - static const MessageCodec codec = _IOSQuickActionsApiCodec(); + final String __pigeon_messageChannelSuffix; /// Sets the dynamic shortcuts for the app. - Future setShortcutItems( - List arg_itemsList) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems', - codec, - binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_itemsList]) as List?; - if (replyList == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyList.length > 1) { + Future setShortcutItems(List itemsList) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([itemsList]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); } else { return; @@ -117,21 +129,23 @@ class IOSQuickActionsApi { /// Removes all dynamic shortcuts. Future clearShortcutItems() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems', - codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; - if (replyList == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyList.length > 1) { + final String __pigeon_channelName = + 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], ); } else { return; @@ -140,22 +154,28 @@ class IOSQuickActionsApi { } abstract class IOSQuickActionsFlutterApi { - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); /// Sends a string representing a shortcut from the native platform to the app. void launchAction(String action); - static void setup(IOSQuickActionsFlutterApi? api, - {BinaryMessenger? binaryMessenger}) { + static void setUp( + IOSQuickActionsFlutterApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction', - codec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMessageHandler(null); + __pigeon_channel.setMessageHandler(null); } else { - channel.setMessageHandler((Object? message) async { + __pigeon_channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction was null.'); final List args = (message as List?)!; diff --git a/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart b/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart index 235591774c5..b002e0ec136 100644 --- a/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart +++ b/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart @@ -29,7 +29,7 @@ class QuickActionsIos extends QuickActionsPlatform { Future initialize(QuickActionHandler handler) async { final _QuickActionHandlerApi quickActionsHandlerApi = _QuickActionHandlerApi(); - IOSQuickActionsFlutterApi.setup(quickActionsHandlerApi); + IOSQuickActionsFlutterApi.setUp(quickActionsHandlerApi); _handler = handler; } diff --git a/packages/quick_actions/quick_actions_ios/pigeons/messages.dart b/packages/quick_actions/quick_actions_ios/pigeons/messages.dart index 553d8552cf7..7cf7d20d587 100644 --- a/packages/quick_actions/quick_actions_ios/pigeons/messages.dart +++ b/packages/quick_actions/quick_actions_ios/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/messages.g.dart', - swiftOut: 'ios/Classes/messages.g.swift', + swiftOut: 'ios/quick_actions_ios/Sources/quick_actions_ios/messages.g.swift', copyrightHeader: 'pigeons/copyright.txt', )) diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml index 3a7cd8f8f16..9f0abd93fe4 100644 --- a/packages/quick_actions/quick_actions_ios/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions_ios description: An implementation for the iOS platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 1.0.10 +version: 1.1.1 environment: sdk: ^3.2.3 @@ -26,7 +26,7 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - pigeon: ^12.0.1 + pigeon: ^20.0.1 plugin_platform_interface: ^2.1.7 topics: diff --git a/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md b/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md index 0cd6236412c..67914343eb9 100644 --- a/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 1.0.6 diff --git a/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml b/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml index 7b222c69477..5fec80c454b 100644 --- a/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml +++ b/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.6 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/rfw/CHANGELOG.md b/packages/rfw/CHANGELOG.md index 7f1da5e2f35..532a7277ebf 100644 --- a/packages/rfw/CHANGELOG.md +++ b/packages/rfw/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.0.29 + +* Adds support for the `Slider` Material widget. + +## 1.0.28 + +* Updates documentation to WidgetStateProperty and ButtonBar. + +## 1.0.27 +* Adds support for `DecorationImage.filterQuality`. + ## 1.0.26 * Supports overriding the error widget builder. diff --git a/packages/rfw/example/hello/android/.gitignore b/packages/rfw/example/hello/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/rfw/example/hello/android/.gitignore +++ b/packages/rfw/example/hello/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/rfw/example/hello/android/build.gradle b/packages/rfw/example/hello/android/build.gradle index 228f12cefbc..8a2e9e183dd 100644 --- a/packages/rfw/example/hello/android/build.gradle +++ b/packages/rfw/example/hello/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/rfw/example/hello/android/settings.gradle b/packages/rfw/example/hello/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/rfw/example/hello/android/settings.gradle +++ b/packages/rfw/example/hello/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/rfw/example/local/android/.gitignore b/packages/rfw/example/local/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/rfw/example/local/android/.gitignore +++ b/packages/rfw/example/local/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/rfw/example/local/android/build.gradle b/packages/rfw/example/local/android/build.gradle index 228f12cefbc..8a2e9e183dd 100644 --- a/packages/rfw/example/local/android/build.gradle +++ b/packages/rfw/example/local/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/rfw/example/local/android/settings.gradle b/packages/rfw/example/local/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/rfw/example/local/android/settings.gradle +++ b/packages/rfw/example/local/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/rfw/example/remote/android/.gitignore b/packages/rfw/example/remote/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/rfw/example/remote/android/.gitignore +++ b/packages/rfw/example/remote/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/rfw/example/remote/android/build.gradle b/packages/rfw/example/remote/android/build.gradle index 228f12cefbc..8a2e9e183dd 100644 --- a/packages/rfw/example/remote/android/build.gradle +++ b/packages/rfw/example/remote/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/rfw/example/remote/android/settings.gradle b/packages/rfw/example/remote/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/rfw/example/remote/android/settings.gradle +++ b/packages/rfw/example/remote/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/rfw/lib/src/flutter/argument_decoders.dart b/packages/rfw/lib/src/flutter/argument_decoders.dart index 41db3bf95b9..e8444ce56b6 100644 --- a/packages/rfw/lib/src/flutter/argument_decoders.dart +++ b/packages/rfw/lib/src/flutter/argument_decoders.dart @@ -550,6 +550,7 @@ class ArgumentDecoders { centerSlice: rect(source, [...key, 'centerSlice']), repeat: enumValue(ImageRepeat.values, source, [...key, 'repeat']) ?? ImageRepeat.noRepeat, matchTextDirection: source.v([...key, 'matchTextDirection']) ?? false, + filterQuality: enumValue(FilterQuality.values, source, [...key, 'filterQuality']) ?? FilterQuality.medium, ); } diff --git a/packages/rfw/lib/src/flutter/material_widgets.dart b/packages/rfw/lib/src/flutter/material_widgets.dart index 1ddd71ad7e6..16132b1ad3c 100644 --- a/packages/rfw/lib/src/flutter/material_widgets.dart +++ b/packages/rfw/lib/src/flutter/material_widgets.dart @@ -22,7 +22,7 @@ import 'runtime.dart'; /// /// * [AboutListTile] /// * [AppBar] -/// * [ButtonBar] +/// * `ButtonBar` /// * [Card] /// * [CircularProgressIndicator] /// * [Divider] @@ -37,6 +37,7 @@ import 'runtime.dart'; /// * [Material] /// * [OutlinedButton] /// * [Scaffold] +/// * [Slider] /// * [TextButton] /// * [VerticalDivider] /// * [OverflowBar] @@ -56,11 +57,11 @@ import 'runtime.dart'; /// Some features have changed in the underlying Flutter's material library and are /// therefore no longer supported, including: /// -/// * The [ButtonBar] widget in the Flutter's material library is planned to be -/// deprecated in favor of the [OverflowBar] widget. The [ButtonBar] widget in +/// * The `ButtonBar` widget in the Flutter's material library is planned to be +/// deprecated in favor of the [OverflowBar] widget. The `ButtonBar` widget in /// `rfw` package uses the [OverflowBar] widget internally for backward compatibility. -/// The [ButtonBar] widget in `rfw` package is not deprecated and will continue to -/// be supported. As a result, the following [ButtonBar] parameters are no longer +/// The `ButtonBar` widget in `rfw` package is not deprecated and will continue to +/// be supported. As a result, the following `ButtonBar` parameters are no longer /// supported: /// /// * `buttonMinWidth` @@ -77,7 +78,7 @@ import 'runtime.dart'; /// * Theming in general is not currently supported. /// /// * Properties whose values are [Animation]s or based on -/// [MaterialStateProperty] are not supported. +/// [WidgetStateProperty] are not supported. /// /// * Features related to focus or configuring mouse support are not /// implemented. @@ -94,7 +95,7 @@ import 'runtime.dart'; /// /// In general, the trend will all of these unsupported features is that this /// library doesn't support features that can't be trivially expressed using the -/// JSON-like structures of RFW. For example, [MaterialStateProperty] is +/// JSON-like structures of RFW. For example, [WidgetStateProperty] is /// designed to be used with code to select the values, which doesn't work well /// in the RFW structure. LocalWidgetLibrary createMaterialWidgets() => LocalWidgetLibrary(_materialWidgetsDefinitions); @@ -142,12 +143,12 @@ Map get _materialWidgetsDefinitions => get _materialWidgetsDefinitions => (['min']) ?? 0.0; + final value = source.v(['value']) ?? min; + final labelText = source.v(['label']); + final label = labelText != null ? '$labelText: ${value.toStringAsFixed(2)}' : value.toStringAsFixed(2); + return Slider( + value: value, + secondaryTrackValue: source.v(['secondaryTrackValue']), + onChanged: source.handler(['onChanged'], + (HandlerTrigger trigger) => (double value) { + trigger({'value': value}); + }), + onChangeStart: source.handler(['onChangeStart'], + (HandlerTrigger trigger) => (double value) { + trigger({'value': value}); + }), + onChangeEnd: source.handler(['onChangeEnd'], + (HandlerTrigger trigger) => (double value) { + trigger({'value': value}); + }), + min: min, + max: source.v(['max']) ?? 1.0, + divisions: source.v(['divisions']), + label: label, + activeColor: ArgumentDecoders.color(source, ['activeColor']), + inactiveColor: ArgumentDecoders.color(source, ['inactiveColor']), + secondaryActiveColor: ArgumentDecoders.color(source, ['secondaryActiveColor']), + thumbColor: ArgumentDecoders.color(source, ['thumbColor']), + allowedInteraction: ArgumentDecoders.enumValue(SliderInteraction.values, source, ['allowedInteraction']), + ); + }, + 'TextButton': (BuildContext context, DataSource source) { // not implemented: buttonStyle, focusNode return TextButton( diff --git a/packages/rfw/pubspec.yaml b/packages/rfw/pubspec.yaml index a83be027aaf..5f217590123 100644 --- a/packages/rfw/pubspec.yaml +++ b/packages/rfw/pubspec.yaml @@ -2,7 +2,7 @@ name: rfw description: "Remote Flutter widgets: a library for rendering declarative widget description files at runtime." repository: https://github.com/flutter/packages/tree/main/packages/rfw issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22 -version: 1.0.26 +version: 1.0.29 environment: sdk: ^3.2.0 diff --git a/packages/rfw/test/argument_decoders_test.dart b/packages/rfw/test/argument_decoders_test.dart index 9906cfa142b..8564db5002d 100644 --- a/packages/rfw/test/argument_decoders_test.dart +++ b/packages/rfw/test/argument_decoders_test.dart @@ -295,6 +295,7 @@ void main() { 1.0, 1.0, 1.0, 1.0, 1.0, ], }, + filterQuality: "none", }, gradient: { type: 'sweep', @@ -314,6 +315,7 @@ void main() { blendMode: "xor", }, onError: event 'image-error-event' { }, + filterQuality: "high", }, gradient: { type: 'linear', @@ -383,13 +385,13 @@ void main() { (tester.widgetList(find.byType(DecoratedBox)).toList()[1].decoration as BoxDecoration).image.toString(), 'DecorationImage(AssetImage(bundle: null, name: "asset"), ' // this just seemed like the easiest way to check all this... 'ColorFilter.matrix([$matrix]), ' - 'Alignment.center, centerSlice: Rect.fromLTRB(5.0, 8.0, 105.0, 78.0), scale 1.0, opacity 1.0, FilterQuality.low)', + 'Alignment.center, centerSlice: Rect.fromLTRB(5.0, 8.0, 105.0, 78.0), scale 1.0, opacity 1.0, FilterQuality.none)', ); expect( (tester.widgetList(find.byType(DecoratedBox)).toList()[0].decoration as BoxDecoration).image.toString(), 'DecorationImage(NetworkImage("x-invalid://", scale: 1.0), ' 'ColorFilter.mode(Color(0xff8811ff), BlendMode.xor), Alignment.center, scale 1.0, ' - 'opacity 1.0, FilterQuality.low)', + 'opacity 1.0, FilterQuality.high)', ); ArgumentDecoders.colorFilterDecoders['custom'] = (DataSource source, List key) { diff --git a/packages/rfw/test/material_widgets_test.dart b/packages/rfw/test/material_widgets_test.dart index 56da9abfd92..f6ad700ed48 100644 --- a/packages/rfw/test/material_widgets_test.dart +++ b/packages/rfw/test/material_widgets_test.dart @@ -202,7 +202,9 @@ void main() { await expectLater( find.byType(MaterialApp), matchesGoldenFile('goldens/material_test.dropdown.png'), - skip: !runGoldens, + // TODO(bparrishMines): Unskip once golden file is updated. See + // https://github.com/flutter/flutter/issues/150127 + skip: !runGoldens || true, ); // Tap on the second item. await tester.tap(find.text('bar')); @@ -566,7 +568,9 @@ void main() { await expectLater( find.byType(RemoteWidget), matchesGoldenFile('goldens/material_test.material_properties.png'), - skip: !runGoldens, + // TODO(bparrishMines): Unskip once golden file is updated. See + // https://github.com/flutter/flutter/issues/150127 + skip: !runGoldens || true, ); runtime.update(testName, parseLibraryFile(''' @@ -586,4 +590,101 @@ void main() { expect(tester.widget(find.byType(Material)).clipBehavior, Clip.antiAlias); }); + + testWidgets('Slider properties', (WidgetTester tester) async { + final Runtime runtime = setupRuntime(); + final DynamicContent data = DynamicContent(); + final List eventLog = []; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: RemoteWidget( + runtime: runtime, + data: data, + widget: const FullyQualifiedWidgetName(testName, 'root'), + onEvent: (String eventName, DynamicMap eventArguments) { + eventLog.add('$eventName $eventArguments'); + }, + ), + ), + ); + expect( + tester.takeException().toString(), + contains('Could not find remote widget named'), + ); + + runtime.update(testName, parseLibraryFile(''' + import core; + import material; + widget root = Scaffold( + body: Center( + child: Slider( + onChanged: event 'slider' { }, + min: 10.0, + max: 100.0, + divisions: 100, + value: 20.0, + activeColor: 0xFF0000FF, + inactiveColor: 0xFF00FF00, + secondaryActiveColor: 0xFFFF0000, + thumbColor: 0xFF000000, + ))); + ''')); + await tester.pump(); + + final Finder sliderFinder = find.byType(Slider); + final Slider slider = tester.widget(sliderFinder); + expect(slider.value, 20.0); + expect(slider.min, 10.0); + expect(slider.max, 100.0); + expect(slider.divisions, 100); + expect(slider.activeColor, const Color(0xFF0000FF)); + expect(slider.inactiveColor, const Color(0xFF00FF00)); + expect(slider.secondaryActiveColor, const Color(0xFFFF0000)); + expect(slider.thumbColor, const Color(0xFF000000)); + + runtime.update(testName, parseLibraryFile(''' + import core; + import material; + + widget root = Scaffold( + body: Container( + child: Slider( + onChanged: event 'slider' { }, + onChangeStart: event 'slider.start' { }, + onChangeEnd: event 'slider.end' { }, + min: 0.0, + max: 100.0, + divisions: 100, + value: 0.0, + ))); + ''')); + await tester.pump(); + + //drag slider + await _slideToValue(tester, sliderFinder, 20.0); + await tester.pumpAndSettle(); + expect(eventLog, + contains(kIsWeb ? 'slider {value: 20}' : 'slider {value: 20.0}')); + expect( + eventLog, + contains( + kIsWeb ? 'slider.start {value: 0}' : 'slider.start {value: 0.0}')); + expect( + eventLog, + contains( + kIsWeb ? 'slider.end {value: 20}' : 'slider.end {value: 20.0}')); + }); +} + +// slide to value for material slider in tests +Future _slideToValue( + WidgetTester widgetTester, Finder slider, double value, + {double paddingOffset = 24.0}) async { + final Offset zeroPoint = widgetTester.getTopLeft(slider) + + Offset(paddingOffset, widgetTester.getSize(slider).height / 2); + final double totalWidth = + widgetTester.getSize(slider).width - (2 * paddingOffset); + final double calculateOffset = value * (totalWidth / 100); + await widgetTester.dragFrom(zeroPoint, Offset(calculateOffset, 0)); } diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index aae16a4b7a0..80572617fd6 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.2.3 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/shared_preferences/shared_preferences/README.md b/packages/shared_preferences/shared_preferences/README.md index 73595966239..1da1215df9e 100644 --- a/packages/shared_preferences/shared_preferences/README.md +++ b/packages/shared_preferences/shared_preferences/README.md @@ -16,7 +16,6 @@ Supported data types are `int`, `double`, `bool`, `String` and `List`. | **Support** | SDK 16+ | 12.0+ | Any | 10.14+ | Any | Any | ## Usage -To use this plugin, add `shared_preferences` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ### Examples Here are small examples that show you how to use the API. diff --git a/packages/shared_preferences/shared_preferences/example/android/.gitignore b/packages/shared_preferences/shared_preferences/example/android/.gitignore index 0a741cb43d6..8e599af9f21 100644 --- a/packages/shared_preferences/shared_preferences/example/android/.gitignore +++ b/packages/shared_preferences/shared_preferences/example/android/.gitignore @@ -7,5 +7,5 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties diff --git a/packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/shared_preferences/shared_preferences/example/android/build.gradle b/packages/shared_preferences/shared_preferences/example/android/build.gradle index 582d60a2faa..d13ef556e26 100644 --- a/packages/shared_preferences/shared_preferences/example/android/build.gradle +++ b/packages/shared_preferences/shared_preferences/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/shared_preferences/shared_preferences/example/android/settings.gradle b/packages/shared_preferences/shared_preferences/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/shared_preferences/shared_preferences/example/android/settings.gradle +++ b/packages/shared_preferences/shared_preferences/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/shared_preferences/shared_preferences/example/macos/Runner/DebugProfile.entitlements b/packages/shared_preferences/shared_preferences/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/shared_preferences/shared_preferences/example/macos/Runner/DebugProfile.entitlements +++ b/packages/shared_preferences/shared_preferences/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/shared_preferences/shared_preferences/example/macos/Runner/Release.entitlements b/packages/shared_preferences/shared_preferences/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/shared_preferences/shared_preferences/example/macos/Runner/Release.entitlements +++ b/packages/shared_preferences/shared_preferences/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/shared_preferences/shared_preferences/example/pubspec.yaml b/packages/shared_preferences/shared_preferences/example/pubspec.yaml index b48401c1b18..d80cc7eacd5 100644 --- a/packages/shared_preferences/shared_preferences/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the shared_preferences plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index b296f4fe917..c3775eb455b 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -6,8 +6,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.2.3 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index eb3dee5054a..aa4279ede83 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.2.3 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 2.2.2 * Updates minSdkVersion to 19. diff --git a/packages/shared_preferences/shared_preferences_android/README.md b/packages/shared_preferences/shared_preferences_android/README.md index 6d30be341c9..d12b09c068a 100644 --- a/packages/shared_preferences/shared_preferences_android/README.md +++ b/packages/shared_preferences/shared_preferences_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/shared_preferences -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java index aecc43991cc..6bfa4e285a6 100644 --- a/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java +++ b/packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java @@ -47,13 +47,6 @@ public SharedPreferencesPlugin() { this.listEncoder = listEncoder; } - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - final SharedPreferencesPlugin plugin = new SharedPreferencesPlugin(); - plugin.setUp(registrar.messenger(), registrar.context()); - } - private void setUp(@NonNull BinaryMessenger messenger, @NonNull Context context) { preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); try { diff --git a/packages/shared_preferences/shared_preferences_android/example/android/.gitignore b/packages/shared_preferences/shared_preferences_android/example/android/.gitignore index 0a741cb43d6..8e599af9f21 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/.gitignore +++ b/packages/shared_preferences/shared_preferences_android/example/android/.gitignore @@ -7,5 +7,5 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties diff --git a/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/shared_preferences/shared_preferences_android/example/android/build.gradle b/packages/shared_preferences/shared_preferences_android/example/android/build.gradle index 3cd5d4a83c4..4dec761be7b 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/shared_preferences/shared_preferences_android/example/android/settings.gradle b/packages/shared_preferences/shared_preferences_android/example/android/settings.gradle index 3a97314b38c..f246a74091b 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/settings.gradle +++ b/packages/shared_preferences/shared_preferences_android/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml index c394ef18c03..0bdf6908628 100644 --- a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the shared_preferences plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index 647d87796cc..201f30884c9 100644 --- a/packages/shared_preferences/shared_preferences_android/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/pubspec.yaml @@ -2,11 +2,11 @@ name: shared_preferences_android description: Android implementation of the shared_preferences plugin repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.2.2 +version: 2.2.3 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md index 317d893cd37..e487278c498 100644 --- a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.4.0 +* Adds Swift Package Manager compatibility. * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. ## 2.3.5 diff --git a/packages/shared_preferences/shared_preferences_foundation/README.md b/packages/shared_preferences/shared_preferences_foundation/README.md index 74dbc4ef0c0..c6da193df8f 100644 --- a/packages/shared_preferences/shared_preferences_foundation/README.md +++ b/packages/shared_preferences/shared_preferences_foundation/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/shared_preferences -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec index 41ec1683c17..008f51f30f4 100644 --- a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec @@ -12,7 +12,7 @@ Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_foundation' } - s.source_files = 'Classes/**/*' + s.source_files = 'shared_preferences_foundation/Sources/shared_preferences_foundation/**/*.swift' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment_target = '12.0' @@ -23,6 +23,6 @@ Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } s.swift_version = '5.0' - s.resource_bundles = {'shared_preferences_foundation_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'shared_preferences_foundation_privacy' => ['shared_preferences_foundation/Sources/shared_preferences_foundation/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Package.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Package.swift new file mode 100644 index 00000000000..eae02a420e8 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import PackageDescription + +let package = Package( + name: "shared_preferences_foundation", + platforms: [ + .iOS("12.0"), + .macOS("10.14"), + ], + products: [ + .library(name: "shared-preferences-foundation", targets: ["shared_preferences_foundation"]) + ], + dependencies: [], + targets: [ + .target( + name: "shared_preferences_foundation", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Resources/PrivacyInfo.xcprivacy b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/shared_preferences/shared_preferences_foundation/darwin/Resources/PrivacyInfo.xcprivacy rename to packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/Resources/PrivacyInfo.xcprivacy diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift similarity index 100% rename from packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift rename to packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift similarity index 100% rename from packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift rename to packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj index 4f0d99270a2..a2ad88eea84 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -215,7 +215,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09bea..8e3ca5dfe19 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { return true diff --git a/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements +++ b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements +++ b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart index 46deeef2bdf..ad8060e3474 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart +++ b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart @@ -7,7 +7,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/messages.g.dart', dartTestOut: 'test/test_api.g.dart', - swiftOut: 'darwin/Classes/messages.g.swift', + swiftOut: + 'darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift', copyrightHeader: 'pigeons/copyright_header.txt', )) @HostApi(dartHostTestHandler: 'TestUserDefaultsApi') diff --git a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml index c94bdae980c..2e5d728e142 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_foundation description: iOS and macOS implementation of the shared_preferences plugin. repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.3.5 +version: 2.4.0 environment: sdk: ^3.2.3 diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md index 122c2a9053d..cabbb80908e 100644 --- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.3.2 diff --git a/packages/shared_preferences/shared_preferences_linux/README.md b/packages/shared_preferences/shared_preferences_linux/README.md index a1bbd9d7ef5..0fe95555e30 100644 --- a/packages/shared_preferences/shared_preferences_linux/README.md +++ b/packages/shared_preferences/shared_preferences_linux/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/shared_preferences -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml index d4ffcd82cbb..060ea7da5f4 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the shared_preferences_linux plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml index 02b6a89dd2d..fab1ffeafd5 100644 --- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.3.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md index d2d7a20fd57..3966f6525b9 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.3.2 diff --git a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml index a027b6ec971..c8c98325957 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.3.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_web/README.md b/packages/shared_preferences/shared_preferences_web/README.md index e9fd0d2caa7..3f45e52e872 100644 --- a/packages/shared_preferences/shared_preferences_web/README.md +++ b/packages/shared_preferences/shared_preferences_web/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/shared_preferences -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/shared_preferences/shared_preferences_web/example/README.md b/packages/shared_preferences/shared_preferences_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/shared_preferences/shared_preferences_web/example/README.md +++ b/packages/shared_preferences/shared_preferences_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md index 52a62eea465..8137dbb765d 100644 --- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.3.2 diff --git a/packages/shared_preferences/shared_preferences_windows/README.md b/packages/shared_preferences/shared_preferences_windows/README.md index de146eb1295..c1ac6a1ffe3 100644 --- a/packages/shared_preferences/shared_preferences_windows/README.md +++ b/packages/shared_preferences/shared_preferences_windows/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/shared_preferences -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml index bb8824bdc8f..8331a914fbf 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the shared_preferences_windows plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml index 86cda786c90..b8481f59830 100644 --- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.3.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/standard_message_codec/CHANGELOG.md b/packages/standard_message_codec/CHANGELOG.md index 6a37c02f14a..a8b04ef3dec 100644 --- a/packages/standard_message_codec/CHANGELOG.md +++ b/packages/standard_message_codec/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.0.1+4 diff --git a/packages/standard_message_codec/example/pubspec.yaml b/packages/standard_message_codec/example/pubspec.yaml index ed3c3f205a0..41b8de76f7b 100644 --- a/packages/standard_message_codec/example/pubspec.yaml +++ b/packages/standard_message_codec/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: standard_message_codec: diff --git a/packages/standard_message_codec/pubspec.yaml b/packages/standard_message_codec/pubspec.yaml index fe6fb43b75f..10e1df4c7c3 100644 --- a/packages/standard_message_codec/pubspec.yaml +++ b/packages/standard_message_codec/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/standard_mess issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Astandard_message_codec environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dev_dependencies: test: ^1.16.0 diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 20fe5bf7840..0bcac837523 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.3.1 + +* Adds generics to the callbacks and builders of TreeView. + +## 0.3.0 + +* Adds new TreeView widget and associated classes. +* New example application for exploring both Trees and Tables. + ## 0.2.1 * Refactors TableSpans to use basic Span classes. Clean up for incoming TreeView. diff --git a/packages/two_dimensional_scrollables/README.md b/packages/two_dimensional_scrollables/README.md index 1b5a0e4b3fd..0b6e8d08f57 100644 --- a/packages/two_dimensional_scrollables/README.md +++ b/packages/two_dimensional_scrollables/README.md @@ -5,8 +5,8 @@ two-dimensional foundation of the Flutter framework. ## Features -This package provides support for a TableView widget that scrolls in both the -vertical and horizontal axes. +This package provides support for TableView and TreeView widgets that scroll +in both the vertical and horizontal axes. ### TableView @@ -14,9 +14,21 @@ vertical and horizontal axes. children lazily in a `TwoDimensionalViewport`. This widget can - Scroll diagonally, or lock axes +- Build infinite rows and columns - Apply decorations to rows and columns - Handle gestures & custom pointers for rows and columns - Pin rows and columns +- Merge table cells + +### TreeView + +`TreeView` is a subclass of `TwoDimensionalScrollView`, building its provided +children lazily in a `TwoDimensionalViewport`. This widget can + +- Scroll diagonally, or lock axes +- Apply decorations to tree rows +- Handle gestures & custom pointers for tree rows +- Animate TreeViewNodes in and out of view ## Getting started @@ -40,12 +52,19 @@ import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; ### TableView -The code in `example/` shows a `TableView` of initially 400 cells, each varying -in sizes with a few `TableSpanDecoration`s like background colors and borders. The -`builder` constructor is called on demand for the cells that are visible in the -TableView. Additional rows can be added on demand while the vertical position -can jump between the first and last row using the buttons at the bottom of the -screen. +The code in `example/lib/table_view` has three `TableView` samples, each +showcasing different features. The `TableExample` demonstrates adding and +removing rows from the table, and applying `TableSpanDecoration`s. The +`MergedTableExample` demonstrates pinned and merged `TableViewCell`s. +Lastly, the `InfiniteTableExample` demonstrates an infinite `TableView`. + +### TreeView + +The code in `example/lib/tree_view` has two `TreeView` samples, each +showcasing different features. The `TreeExample` demonstrates most of +the default builders and animations. The `CustomTreeExample` demonstrates +a highly customized tree, utilizing `TreeView.treeNodeBuilder`, +`TreeView.treeRowBuilder` and `TreeView.onNodeToggle`. ## Changelog diff --git a/packages/two_dimensional_scrollables/example/.pluginToolsConfig.yaml b/packages/two_dimensional_scrollables/example/.pluginToolsConfig.yaml new file mode 100644 index 00000000000..4f59e743100 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/.pluginToolsConfig.yaml @@ -0,0 +1,4 @@ +# TODO(Piinks): Remove once https://github.com/flutter/flutter/pull/147202 reaches stable +buildFlags: + global: + - "--no-tree-shake-icons" diff --git a/packages/two_dimensional_scrollables/example/README.md b/packages/two_dimensional_scrollables/example/README.md index 0f267cb31f7..e872332ecf6 100644 --- a/packages/two_dimensional_scrollables/example/README.md +++ b/packages/two_dimensional_scrollables/example/README.md @@ -1,3 +1,3 @@ -# TableView Example +# TableView and TreeView Examples -A sample application that utilizes the TableView API. +A sample application that utilizes the TableView and TreeView APIs. diff --git a/packages/two_dimensional_scrollables/example/android/.gitignore b/packages/two_dimensional_scrollables/example/android/.gitignore index 6f568019d3c..55afd919c65 100644 --- a/packages/two_dimensional_scrollables/example/android/.gitignore +++ b/packages/two_dimensional_scrollables/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/two_dimensional_scrollables/example/android/build.gradle b/packages/two_dimensional_scrollables/example/android/build.gradle index 582d60a2faa..d13ef556e26 100644 --- a/packages/two_dimensional_scrollables/example/android/build.gradle +++ b/packages/two_dimensional_scrollables/example/android/build.gradle @@ -13,7 +13,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/two_dimensional_scrollables/example/android/settings.gradle b/packages/two_dimensional_scrollables/example/android/settings.gradle index 5315a9b79e4..1cb1f7a2391 100644 --- a/packages/two_dimensional_scrollables/example/android/settings.gradle +++ b/packages/two_dimensional_scrollables/example/android/settings.gradle @@ -10,7 +10,7 @@ def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/two_dimensional_scrollables/example/lib/main.dart b/packages/two_dimensional_scrollables/example/lib/main.dart index ff714af1e49..a065a41938e 100644 --- a/packages/two_dimensional_scrollables/example/lib/main.dart +++ b/packages/two_dimensional_scrollables/example/lib/main.dart @@ -2,189 +2,70 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; -// Print statements are only for illustrative purposes, not recommended for -// production applications. -// ignore_for_file: avoid_print +import 'table_view/table_explorer.dart'; +import 'tree_view/tree_explorer.dart'; void main() { - runApp(const TableExampleApp()); + runApp(const ExampleApp()); } -/// A sample application that utilizes the TableView API. -class TableExampleApp extends StatelessWidget { - /// Creates an instance of the TableView example app. - const TableExampleApp({super.key}); +/// A sample application that utilizes the TableView and TreeView APIs. +class ExampleApp extends StatelessWidget { + /// Creates an instance of the example app. + const ExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( - title: 'Table Example', + title: '2D Scrolling Examples', theme: ThemeData( - useMaterial3: true, + colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple), + appBarTheme: AppBarTheme(backgroundColor: Colors.purple[50]), ), - home: const TableExample(), + home: const ExampleHome(), + routes: { + '/table': (BuildContext context) => const TableExplorer(), + '/tree': (BuildContext context) => const TreeExplorer(), + }, ); } } -/// The class containing the TableView for the sample application. -class TableExample extends StatefulWidget { +/// The home page of the application, which directs to the tree or table +/// explorer. +class ExampleHome extends StatelessWidget { /// Creates a screen that demonstrates the TableView widget. - const TableExample({super.key}); - - @override - State createState() => _TableExampleState(); -} - -class _TableExampleState extends State { - late final ScrollController _verticalController = ScrollController(); - int _rowCount = 20; + const ExampleHome({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Table Example'), - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 50), - child: TableView.builder( - verticalDetails: - ScrollableDetails.vertical(controller: _verticalController), - cellBuilder: _buildCell, - columnCount: 20, - columnBuilder: _buildColumnSpan, - rowCount: _rowCount, - rowBuilder: _buildRowSpan, - ), - ), - persistentFooterButtons: [ - TextButton( - onPressed: () { - _verticalController.jumpTo(0); - }, - child: const Text('Jump to Top'), - ), - TextButton( - onPressed: () { - _verticalController - .jumpTo(_verticalController.position.maxScrollExtent); - }, - child: const Text('Jump to Bottom'), - ), - TextButton( - onPressed: () { - setState(() { - _rowCount += 10; - }); - }, - child: const Text('Add 10 Rows'), - ), - ], - ); - } - - TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) { - return TableViewCell( - child: Center( - child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'), + title: const Text('Tables & Trees'), ), - ); - } - - TableSpan _buildColumnSpan(int index) { - const TableSpanDecoration decoration = TableSpanDecoration( - border: TableSpanBorder( - trailing: BorderSide(), + body: Center( + child: Column(children: [ + const Spacer(flex: 3), + FilledButton( + onPressed: () { + // Go to table explorer + Navigator.of(context).pushNamed('/table'); + }, + child: const Text('TableView Explorer'), + ), + const Spacer(), + FilledButton( + onPressed: () { + // Go to tree explorer + Navigator.of(context).pushNamed('/tree'); + }, + child: const Text('TreeView Explorer'), + ), + const Spacer(flex: 3), + ]), ), ); - - switch (index % 5) { - case 0: - return TableSpan( - foregroundDecoration: decoration, - extent: const FixedTableSpanExtent(100), - onEnter: (_) => print('Entered column $index'), - recognizerFactories: { - TapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(), - (TapGestureRecognizer t) => - t.onTap = () => print('Tap column $index'), - ), - }, - ); - case 1: - return TableSpan( - foregroundDecoration: decoration, - extent: const FractionalTableSpanExtent(0.5), - onEnter: (_) => print('Entered column $index'), - cursor: SystemMouseCursors.contextMenu, - ); - case 2: - return TableSpan( - foregroundDecoration: decoration, - extent: const FixedTableSpanExtent(120), - onEnter: (_) => print('Entered column $index'), - ); - case 3: - return TableSpan( - foregroundDecoration: decoration, - extent: const FixedTableSpanExtent(145), - onEnter: (_) => print('Entered column $index'), - ); - case 4: - return TableSpan( - foregroundDecoration: decoration, - extent: const FixedTableSpanExtent(200), - onEnter: (_) => print('Entered column $index'), - ); - } - throw AssertionError( - 'This should be unreachable, as every index is accounted for in the switch clauses.'); - } - - TableSpan _buildRowSpan(int index) { - final TableSpanDecoration decoration = TableSpanDecoration( - color: index.isEven ? Colors.purple[100] : null, - border: const TableSpanBorder( - trailing: BorderSide( - width: 3, - ), - ), - ); - - switch (index % 3) { - case 0: - return TableSpan( - backgroundDecoration: decoration, - extent: const FixedTableSpanExtent(50), - recognizerFactories: { - TapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(), - (TapGestureRecognizer t) => - t.onTap = () => print('Tap row $index'), - ), - }, - ); - case 1: - return TableSpan( - backgroundDecoration: decoration, - extent: const FixedTableSpanExtent(65), - cursor: SystemMouseCursors.click, - ); - case 2: - return TableSpan( - backgroundDecoration: decoration, - extent: const FractionalTableSpanExtent(0.15), - ); - } - throw AssertionError( - 'This should be unreachable, as every index is accounted for in the switch clauses.'); } } diff --git a/packages/two_dimensional_scrollables/example/lib/table_view/infinite_table.dart b/packages/two_dimensional_scrollables/example/lib/table_view/infinite_table.dart new file mode 100644 index 00000000000..97099139480 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/table_view/infinite_table.dart @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +/// The class demonstrating an infinite number of rows and columns in +/// TableView. +class InfiniteTableExample extends StatefulWidget { + /// Creates a screen that demonstrates an infinite TableView widget. + const InfiniteTableExample({super.key}); + + @override + State createState() => _InfiniteExampleState(); +} + +class _InfiniteExampleState extends State { + int? _rowCount; + int? _columnCount; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: TableView.builder( + cellBuilder: _buildCell, + columnCount: _columnCount, + columnBuilder: _buildSpan, + rowCount: _rowCount, + rowBuilder: _buildSpan, + diagonalDragBehavior: DiagonalDragBehavior.free, + ), + persistentFooterAlignment: AlignmentDirectional.center, + persistentFooterButtons: [ + Text( + 'Column count is ${_columnCount == null ? 'infinite' : '50 '}', + style: const TextStyle(fontStyle: FontStyle.italic), + ), + FilledButton( + onPressed: () { + setState(() { + if (_columnCount != null) { + _columnCount = null; + } else { + _columnCount = 50; + } + }); + }, + child: Text( + 'Make columns ${_columnCount == null ? 'fixed' : 'infinite'}', + ), + ), + const SizedBox.square(dimension: 10), + Text( + 'Row count is ${_rowCount == null ? 'infinite' : '50 '}', + style: const TextStyle(fontStyle: FontStyle.italic), + ), + FilledButton( + onPressed: () { + setState(() { + if (_rowCount != null) { + _rowCount = null; + } else { + _rowCount = 50; + } + }); + }, + child: Text( + 'Make rows ${_rowCount == null ? 'fixed' : 'infinite'}', + ), + ), + ], + ); + } + + TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) { + final Color boxColor = + switch ((vicinity.row.isEven, vicinity.column.isEven)) { + (true, false) || (false, true) => Colors.white, + (false, false) => Colors.indigo[100]!, + (true, true) => Colors.indigo[200]! + }; + return TableViewCell( + child: ColoredBox( + color: boxColor, + child: Center( + child: Text('${vicinity.column}:${vicinity.row}'), + ), + ), + ); + } + + TableSpan _buildSpan(int index) { + return const TableSpan(extent: FixedTableSpanExtent(100)); + } +} diff --git a/packages/two_dimensional_scrollables/example/lib/table_view/merged_table.dart b/packages/two_dimensional_scrollables/example/lib/table_view/merged_table.dart new file mode 100644 index 00000000000..e5b718ad74b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/table_view/merged_table.dart @@ -0,0 +1,145 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +/// The class demonstrating merged cells in TableView. +class MergedTableExample extends StatefulWidget { + /// Creates a screen that shows a color palette in the TableView widget. + const MergedTableExample({super.key}); + + @override + State createState() => _MergedTableExampleState(); +} + +class _MergedTableExampleState extends State { + ({String name, Color color}) _getColorForVicinity(TableVicinity vicinity) { + final int colorIndex = (vicinity.row / 3).floor(); + final MaterialColor primary = Colors.primaries[colorIndex]; + if (vicinity.column == 0) { + // Leading primary color + return ( + color: primary[500]!, + name: '${_getPrimaryNameFor(colorIndex)}, 500', + ); + } + final int leadingRow = colorIndex * 3; + final int middleRow = leadingRow + 1; + int? colorValue; + if (vicinity.row == leadingRow) { + colorValue = switch (vicinity.column) { + 1 => 50, + 2 => 100, + 3 => 200, + _ => throw AssertionError('This should be unreachable.'), + }; + } else if (vicinity.row == middleRow) { + colorValue = switch (vicinity.column) { + 1 => 300, + 2 => 400, + 3 => 600, + _ => throw AssertionError('This should be unreachable.'), + }; + } else { + // last row + colorValue = switch (vicinity.column) { + 1 => 700, + 2 => 800, + 3 => 900, + _ => throw AssertionError('This should be unreachable.'), + }; + } + return (color: primary[colorValue]!, name: colorValue.toString()); + } + + String _getPrimaryNameFor(int index) { + return switch (index) { + 0 => 'Red', + 1 => 'Pink', + 2 => 'Purple', + 3 => 'DeepPurple', + 4 => 'Indigo', + 5 => 'Blue', + 6 => 'LightBlue', + 7 => 'Cyan', + 8 => 'Teal', + 9 => 'Green', + 10 => 'LightGreen', + 11 => 'Lime', + 12 => 'Yellow', + 13 => 'Amber', + 14 => 'Orange', + 15 => 'DeepOrange', + 16 => 'Brown', + 17 => 'BlueGrey', + _ => throw AssertionError('This should be unreachable.'), + }; + } + + @override + Widget build(BuildContext context) { + final Size screenSize = MediaQuery.sizeOf(context); + return Scaffold( + body: Padding( + padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.15), + child: TableView.builder( + cellBuilder: _buildCell, + columnCount: 4, + pinnedColumnCount: 1, + columnBuilder: _buildColumnSpan, + rowCount: 51, // 17 primary colors * 3 rows each + rowBuilder: _buildRowSpan, + ), + ), + ); + } + + TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) { + final int colorIndex = (vicinity.row / 3).floor(); + final ({String name, Color color}) cell = _getColorForVicinity(vicinity); + final Color textColor = + ThemeData.estimateBrightnessForColor(cell.color) == Brightness.light + ? Colors.black + : Colors.white; + final TextStyle style = TextStyle( + color: textColor, + fontSize: 18.0, + fontWeight: vicinity.column == 0 ? FontWeight.bold : null, + ); + return TableViewCell( + rowMergeStart: vicinity.column == 0 ? colorIndex * 3 : null, + rowMergeSpan: vicinity.column == 0 ? 3 : null, + child: ColoredBox( + color: cell.color, + child: Center( + child: Text(cell.name, style: style), + ), + ), + ); + } + + TableSpan _buildColumnSpan(int index) { + return TableSpan( + extent: FixedTableSpanExtent(index == 0 ? 220 : 180), + foregroundDecoration: index == 0 + ? const TableSpanDecoration( + border: TableSpanBorder( + trailing: BorderSide( + width: 5, + color: Colors.white, + ), + ), + ) + : null, + ); + } + + TableSpan _buildRowSpan(int index) { + return TableSpan( + extent: const FixedTableSpanExtent(120), + padding: index % 3 == 0 ? const TableSpanPadding(leading: 5.0) : null, + ); + } +} diff --git a/packages/two_dimensional_scrollables/example/lib/table_view/simple_table.dart b/packages/two_dimensional_scrollables/example/lib/table_view/simple_table.dart new file mode 100644 index 00000000000..5e23bb20e95 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/table_view/simple_table.dart @@ -0,0 +1,178 @@ +// 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/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +// Print statements are only for illustrative purposes, not recommended for +// production applications. +// ignore_for_file: avoid_print + +/// The class containing the TableView for the sample application. +class TableExample extends StatefulWidget { + /// Creates a screen that demonstrates the TableView widget. + const TableExample({super.key}); + + @override + State createState() => _TableExampleState(); +} + +class _TableExampleState extends State { + late final ScrollController _verticalController = ScrollController(); + int _rowCount = 20; + + @override + void dispose() { + _verticalController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + child: TableView.builder( + verticalDetails: ScrollableDetails.vertical( + controller: _verticalController, + ), + cellBuilder: _buildCell, + columnCount: 20, + columnBuilder: _buildColumnSpan, + rowCount: _rowCount, + rowBuilder: _buildRowSpan, + ), + ), + persistentFooterButtons: [ + TextButton( + onPressed: () { + _verticalController.jumpTo(0); + }, + child: const Text('Jump to Top'), + ), + TextButton( + onPressed: () { + _verticalController.jumpTo( + _verticalController.position.maxScrollExtent, + ); + }, + child: const Text('Jump to Bottom'), + ), + TextButton( + onPressed: () { + setState(() { + _rowCount += 10; + }); + }, + child: const Text('Add 10 Rows'), + ), + ], + ); + } + + TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) { + return TableViewCell( + child: Center( + child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'), + ), + ); + } + + TableSpan _buildColumnSpan(int index) { + const TableSpanDecoration decoration = TableSpanDecoration( + border: TableSpanBorder( + trailing: BorderSide(), + ), + ); + + switch (index % 5) { + case 0: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(100), + onEnter: (_) => print('Entered column $index'), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => + t.onTap = () => print('Tap column $index'), + ), + }, + ); + case 1: + return TableSpan( + foregroundDecoration: decoration, + extent: const FractionalTableSpanExtent(0.5), + onEnter: (_) => print('Entered column $index'), + cursor: SystemMouseCursors.contextMenu, + ); + case 2: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(120), + onEnter: (_) => print('Entered column $index'), + ); + case 3: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(145), + onEnter: (_) => print('Entered column $index'), + ); + case 4: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(200), + onEnter: (_) => print('Entered column $index'), + ); + } + throw AssertionError( + 'This should be unreachable, as every index is accounted for in the ' + 'switch clauses.', + ); + } + + TableSpan _buildRowSpan(int index) { + final TableSpanDecoration decoration = TableSpanDecoration( + color: index.isEven ? Colors.purple[100] : null, + border: const TableSpanBorder( + trailing: BorderSide( + width: 3, + ), + ), + ); + + switch (index % 3) { + case 0: + return TableSpan( + backgroundDecoration: decoration, + extent: const FixedTableSpanExtent(50), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => + t.onTap = () => print('Tap row $index'), + ), + }, + ); + case 1: + return TableSpan( + backgroundDecoration: decoration, + extent: const FixedTableSpanExtent(65), + cursor: SystemMouseCursors.click, + ); + case 2: + return TableSpan( + backgroundDecoration: decoration, + extent: const FractionalTableSpanExtent(0.15), + ); + } + throw AssertionError( + 'This should be unreachable, as every index is accounted for in the ' + 'switch clauses.', + ); + } +} diff --git a/packages/two_dimensional_scrollables/example/lib/table_view/table_explorer.dart b/packages/two_dimensional_scrollables/example/lib/table_view/table_explorer.dart new file mode 100644 index 00000000000..1536fa88fca --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/table_view/table_explorer.dart @@ -0,0 +1,113 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'infinite_table.dart'; +import 'merged_table.dart'; +import 'simple_table.dart'; + +/// The page containing the interactive controls that modify the sample +/// TableView. +class TableExplorer extends StatefulWidget { + /// Creates a screen that demonstrates the TableView widget in varying + /// configurations. + const TableExplorer({super.key}); + + @override + State createState() => _TableExplorerState(); +} + +/// Which example is being displayed. +enum TableType { + /// Displays TableExample. + simple, + + /// Displays MergedTableExample. + merged, + + /// Displays InfiniteTableExample. + infinite, +} + +class _TableExplorerState extends State { + final SizedBox _spacer = const SizedBox.square(dimension: 20.0); + TableType _currentExample = TableType.simple; + String _getTitle() { + return switch (_currentExample) { + TableType.simple => 'Simple TableView', + TableType.merged => 'Merged cells in TableView', + TableType.infinite => 'Infinite TableView', + }; + } + + Widget _getTable() { + return switch (_currentExample) { + TableType.simple => const TableExample(), + TableType.merged => const MergedTableExample(), + TableType.infinite => const InfiniteTableExample(), + }; + } + + Widget _getRadioRow() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Spacer(), + Radio( + value: TableType.simple, + groupValue: _currentExample, + onChanged: (TableType? value) { + setState(() { + _currentExample = value!; + }); + }, + ), + const Text('Simple'), + _spacer, + Radio( + value: TableType.merged, + groupValue: _currentExample, + onChanged: (TableType? value) { + setState(() { + _currentExample = value!; + }); + }, + ), + const Text('Merged'), + _spacer, + Radio( + value: TableType.infinite, + groupValue: _currentExample, + onChanged: (TableType? value) { + setState(() { + _currentExample = value!; + }); + }, + ), + const Text('Infinite'), + const Spacer(), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_getTitle()), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(50), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: _getRadioRow(), + ), + ), + ), + body: _getTable(), + ); + } +} diff --git a/packages/two_dimensional_scrollables/example/lib/tree_view/custom_tree.dart b/packages/two_dimensional_scrollables/example/lib/tree_view/custom_tree.dart new file mode 100644 index 00000000000..cbca2fb35dd --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/tree_view/custom_tree.dart @@ -0,0 +1,282 @@ +// 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/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +/// The class containing a TreeView that highlights the selected node. +/// The custom TreeView.treeNodeBuilder makes tapping the whole row of a parent +/// toggle the node open and closed with TreeView.toggleNodeWith. The +/// scrollbars will appear as the content exceeds the bounds of the viewport. +class CustomTreeExample extends StatefulWidget { + /// Creates a screen that demonstrates the TreeView widget. + const CustomTreeExample({super.key}); + + @override + State createState() => CustomTreeExampleState(); +} + +/// The state of the [CustomTreeExample]. +class CustomTreeExampleState extends State { + /// The [TreeViewController] associated with this [TreeView]. + @visibleForTesting + final TreeViewController treeController = TreeViewController(); + + /// The [ScrollController] associated with the vertical axis. + @visibleForTesting + final ScrollController verticalController = ScrollController(); + + TreeViewNode? _selectedNode; + final ScrollController _horizontalController = ScrollController(); + final List> _tree = >[ + TreeViewNode('README.md'), + TreeViewNode('analysis_options.yaml'), + TreeViewNode( + 'lib', + children: >[ + TreeViewNode( + 'src', + children: >[ + TreeViewNode( + 'common', + children: >[ + TreeViewNode('span.dart'), + ], + ), + TreeViewNode( + 'table_view', + children: >[ + TreeViewNode('table_cell.dart'), + TreeViewNode('table_delegate.dart'), + TreeViewNode('table_span.dart'), + TreeViewNode('table.dart'), + ], + ), + TreeViewNode( + 'tree_view', + children: >[ + TreeViewNode('render_tree.dart'), + TreeViewNode('tree_core.dart'), + TreeViewNode('tree_delegate.dart'), + TreeViewNode('tree_span.dart'), + TreeViewNode('tree.dart'), + ], + ), + ], + ), + TreeViewNode('two_dimensional_scrollables.dart'), + ], + ), + TreeViewNode('pubspec.lock'), + TreeViewNode('pubspec.yaml'), + TreeViewNode( + 'test', + children: >[ + TreeViewNode( + 'common', + children: >[ + TreeViewNode('span_test.dart'), + ], + ), + TreeViewNode( + 'table_view', + children: >[ + TreeViewNode('table_cell_test.dart'), + TreeViewNode('table_delegate_test.dart'), + TreeViewNode('table_span_test.dart'), + TreeViewNode('table_test.dart'), + ], + ), + TreeViewNode( + 'tree_view', + children: >[ + TreeViewNode('render_tree_test.dart'), + TreeViewNode('tree_core_test.dart'), + TreeViewNode('tree_delegate_test.dart'), + TreeViewNode('tree_span_test.dart'), + TreeViewNode('tree_test.dart'), + ], + ), + ], + ), + ]; + + Widget _treeNodeBuilder( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + final bool isParentNode = node.children.isNotEmpty; + final BorderSide border = BorderSide( + width: 2, + color: Colors.purple[300]!, + ); + // TRY THIS: TreeView.toggleNodeWith can be wrapped around any Widget (even + // the whole row) to trigger parent nodes to toggle opened and closed. + // Currently, the toggle is triggered in _getTapRecognizer below using the + // TreeViewController. + return Row( + children: [ + // Custom indentation + SizedBox(width: 10.0 * node.depth! + 8.0), + DecoratedBox( + decoration: BoxDecoration( + border: node.parent != null + ? Border(left: border, bottom: border) + : null, + ), + child: const SizedBox(height: 50.0, width: 20.0), + ), + // Leading icon for parent nodes + if (isParentNode) + DecoratedBox( + decoration: BoxDecoration(border: Border.all()), + child: SizedBox.square( + dimension: 20.0, + child: Icon( + node.isExpanded ? Icons.remove : Icons.add, + size: 14, + ), + ), + ), + // Spacer + const SizedBox(width: 8.0), + // Content + Text(node.content), + ], + ); + } + + Map _getTapRecognizer( + TreeViewNode node, + ) { + return { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => t.onTap = () { + setState(() { + // Toggling the node here instead means any tap on the row can + // toggle parent nodes opened and closed. + treeController.toggleNode(node); + _selectedNode = node; + }); + }, + ), + }; + } + + Widget _getTree() { + return DecoratedBox( + decoration: BoxDecoration( + border: Border.all(), + ), + child: Scrollbar( + controller: _horizontalController, + thumbVisibility: true, + child: Scrollbar( + controller: verticalController, + thumbVisibility: true, + child: TreeView( + controller: treeController, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: _horizontalController, + ), + tree: _tree, + onNodeToggle: (TreeViewNode node) { + setState(() { + _selectedNode = node; + }); + }, + treeNodeBuilder: _treeNodeBuilder, + treeRowBuilder: (TreeViewNode node) { + if (_selectedNode == node) { + return TreeRow( + extent: FixedTreeRowExtent( + node.children.isNotEmpty ? 60.0 : 50.0, + ), + recognizerFactories: _getTapRecognizer(node), + backgroundDecoration: TreeRowDecoration( + color: Colors.amber[100], + ), + foregroundDecoration: const TreeRowDecoration( + border: TreeRowBorder.all(BorderSide())), + ); + } + return TreeRow( + extent: FixedTreeRowExtent( + node.children.isNotEmpty ? 60.0 : 50.0, + ), + recognizerFactories: _getTapRecognizer(node), + ); + }, + // No internal indentation, the custom treeNodeBuilder applies its + // own indentation to decorate in the indented space. + indentation: TreeViewIndentationType.none, + ), + ), + ), + ); + } + + @override + void dispose() { + verticalController.dispose(); + _horizontalController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // This example is assumes the full screen is available. + final Size screenSize = MediaQuery.sizeOf(context); + final List selectedChildren = []; + if (_selectedNode != null) { + selectedChildren.addAll([ + const Spacer(), + Icon( + _selectedNode!.children.isEmpty + ? Icons.file_open_outlined + : Icons.folder_outlined, + ), + const SizedBox(height: 25.0), + Text(_selectedNode!.content), + const Spacer(), + ]); + } + return Scaffold( + body: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 25.0), + child: Row(children: [ + SizedBox( + width: (screenSize.width - 50) / 2, + height: double.infinity, + child: _getTree(), + ), + DecoratedBox( + decoration: BoxDecoration( + border: Border.all(), + ), + child: SizedBox( + width: (screenSize.width - 50) / 2, + height: double.infinity, + child: Center( + child: Column( + children: selectedChildren, + ), + ), + ), + ), + ]), + ), + ), + ); + } +} diff --git a/packages/two_dimensional_scrollables/example/lib/tree_view/simple_tree.dart b/packages/two_dimensional_scrollables/example/lib/tree_view/simple_tree.dart new file mode 100644 index 00000000000..c5e0fe85a8c --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/tree_view/simple_tree.dart @@ -0,0 +1,152 @@ +// 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/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +/// The class containing a TreeView that highlights the selected row. The +/// default TreeView.treeNodeBuilder, makes tapping the leading icon of a parent +/// toggle the node open and closed. The scrollbars will appear as the content +/// exceeds the bounds of the viewport. +class TreeExample extends StatefulWidget { + /// Creates a screen that demonstrates the TreeView widget. + const TreeExample({super.key}); + + @override + State createState() => TreeExampleState(); +} + +/// The state of the [TreeExample]. +class TreeExampleState extends State { + /// The [TreeViewController] associated with this [TreeView]. + @visibleForTesting + final TreeViewController treeController = TreeViewController(); + + /// The [ScrollController] associated with the horizontal axis. + @visibleForTesting + final ScrollController horizontalController = ScrollController(); + TreeViewNode? _selectedNode; + final ScrollController _verticalController = ScrollController(); + final List> _tree = >[ + TreeViewNode( + "It's supercalifragilisticexpialidocious", + children: >[ + TreeViewNode( + 'Even though the sound of it is something quite atrocious', + ), + TreeViewNode( + "If you say it loud enough you'll always sound precocious", + ), + ], + ), + TreeViewNode( + 'Supercalifragilisticexpialidocious', + children: >[ + TreeViewNode( + 'Um-dittle-ittl-um-dittle-I', + children: >[ + TreeViewNode( + 'Um-dittle-ittl-um-dittle-I', + children: >[ + TreeViewNode( + 'Um-dittle-ittl-um-dittle-I', + children: >[ + TreeViewNode( + 'Um-dittle-ittl-um-dittle-I', + ), + ], + ), + ], + ), + ], + ), + ], + ), + ]; + + Map _getTapRecognizer( + TreeViewNode node, + ) { + return { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => t.onTap = () { + setState(() { + _selectedNode = node; + }); + }, + ), + }; + } + + Widget _getTree() { + return DecoratedBox( + decoration: BoxDecoration(border: Border.all()), + child: Scrollbar( + controller: horizontalController, + thumbVisibility: true, + child: Scrollbar( + controller: _verticalController, + thumbVisibility: true, + child: TreeView( + controller: treeController, + verticalDetails: ScrollableDetails.vertical( + controller: _verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + tree: _tree, + onNodeToggle: (TreeViewNode node) { + setState(() { + _selectedNode = node; + }); + }, + treeRowBuilder: (TreeViewNode node) { + if (_selectedNode == node) { + return TreeView.defaultTreeRowBuilder(node).copyWith( + recognizerFactories: _getTapRecognizer(node), + backgroundDecoration: TreeRowDecoration( + color: Colors.purple[100], + ), + ); + } + return TreeView.defaultTreeRowBuilder(node).copyWith( + recognizerFactories: _getTapRecognizer(node), + ); + }, + // Exaggerated indentation to exceed viewport bounds. + indentation: TreeViewIndentationType.custom(50.0), + ), + ), + ), + ); + } + + @override + void dispose() { + _verticalController.dispose(); + horizontalController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Size screenSize = MediaQuery.sizeOf(context); + return Scaffold( + body: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: screenSize.width * 0.25, + vertical: 25.0, + ), + child: _getTree(), + ), + ), + ); + } +} diff --git a/packages/two_dimensional_scrollables/example/lib/tree_view/tree_explorer.dart b/packages/two_dimensional_scrollables/example/lib/tree_view/tree_explorer.dart new file mode 100644 index 00000000000..ceee073685b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/tree_view/tree_explorer.dart @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'custom_tree.dart'; +import 'simple_tree.dart'; + +/// The page containing the interactive controls that modify the sample +/// TreeView. +class TreeExplorer extends StatefulWidget { + /// Creates a screen that demonstrates the TreeView widget in varying + /// configurations. + const TreeExplorer({super.key}); + + @override + State createState() => _TreeExplorerState(); +} + +/// Which example is being displayed. +enum TreeType { + /// Displays TreeExample. + simple, + + /// Displays CustomTreeExample. + custom, +} + +class _TreeExplorerState extends State { + final SizedBox _spacer = const SizedBox.square(dimension: 20.0); + TreeType _currentExample = TreeType.simple; + String _getTitle() { + return switch (_currentExample) { + TreeType.simple => 'Simple TreeView', + TreeType.custom => 'Customizing TreeView', + }; + } + + Widget _getTree() { + return switch (_currentExample) { + TreeType.simple => const TreeExample(), + TreeType.custom => const CustomTreeExample(), + }; + } + + Widget _getRadioRow() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Spacer(), + Radio( + value: TreeType.simple, + groupValue: _currentExample, + onChanged: (TreeType? value) { + setState(() { + _currentExample = value!; + }); + }, + ), + const Text('Simple'), + _spacer, + Radio( + value: TreeType.custom, + groupValue: _currentExample, + onChanged: (TreeType? value) { + setState(() { + _currentExample = value!; + }); + }, + ), + const Text('Custom'), + const Spacer(), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_getTitle()), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(50), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: _getRadioRow(), + ), + ), + ), + body: _getTree(), + ); + } +} diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj index 27e0f506b60..7d9ca676a43 100644 --- a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 397f3d339fd..15368eccb25 100644 --- a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { return true diff --git a/packages/two_dimensional_scrollables/example/pubspec.yaml b/packages/two_dimensional_scrollables/example/pubspec.yaml index 675a9c3991f..864ffc566cf 100644 --- a/packages/two_dimensional_scrollables/example/pubspec.yaml +++ b/packages/two_dimensional_scrollables/example/pubspec.yaml @@ -1,13 +1,13 @@ -name: table_view_example -description: 'A sample application that uses TableView' +name: two_dimensional_examples +description: 'A sample application that uses TableView and TreeView' publish_to: 'none' # The following defines the version and build number for your application. -version: 1.0.0+1 +version: 2.0.0 environment: - sdk: '>=3.2.0 <4.0.0' - flutter: ">=3.16.0" + sdk: '>=3.3.0 <4.0.0' + flutter: ">=3.19.0" dependencies: flutter: diff --git a/packages/two_dimensional_scrollables/example/test/main_test.dart b/packages/two_dimensional_scrollables/example/test/main_test.dart new file mode 100644 index 00000000000..b5cacfecccc --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/main_test.dart @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/main.dart'; + +void main() { + testWidgets('Main builds & buttons work', (WidgetTester tester) async { + await tester.pumpWidget(const ExampleApp()); + expect(find.text('TableView Explorer'), findsOneWidget); + expect(find.text('TreeView Explorer'), findsOneWidget); + + await tester.tap(find.text('TableView Explorer')); + await tester.pumpAndSettle(); + // First example on the TableView page. + expect(find.text('Simple TableView'), findsOneWidget); + await tester.tap(find.byType(BackButton)); + await tester.pumpAndSettle(); + expect(find.text('Simple TableView'), findsNothing); + await tester.tap(find.text('TreeView Explorer')); + await tester.pumpAndSettle(); + // First example on the TreeView page. + expect(find.text('Simple TreeView'), findsOneWidget); + }); +} diff --git a/packages/two_dimensional_scrollables/example/test/table_view/infinite_table_test.dart b/packages/two_dimensional_scrollables/example/test/table_view/infinite_table_test.dart new file mode 100644 index 00000000000..1f0f5e6b676 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/table_view/infinite_table_test.dart @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/table_view/infinite_table.dart'; + +void main() { + testWidgets('Builds, can scroll, buttons work', (WidgetTester tester) async { + tester.view.physicalSize = const Size.square(800.0); + await tester.pumpWidget(const MaterialApp(home: InfiniteTableExample())); + await tester.pump(); + expect(find.text('0:0'), findsOneWidget); + + final Finder verticalScrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.down; + } + return false; + }); + final ScrollPosition verticalPosition = + (tester.state(verticalScrollable) as ScrollableState).position; + + final Finder horizontalScrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.right; + } + return false; + }); + final ScrollPosition horizontalPosition = + (tester.state(horizontalScrollable) as ScrollableState).position; + + expect(verticalPosition.pixels, 0.0); + expect(verticalPosition.maxScrollExtent, double.infinity); + expect(horizontalPosition.pixels, 0.0); + expect(horizontalPosition.maxScrollExtent, double.infinity); + verticalPosition.jumpTo(300.0); + horizontalPosition.jumpTo(20.0); + await tester.pump(); + expect(verticalPosition.pixels, 300.0); + expect(verticalPosition.maxScrollExtent, double.infinity); + expect(horizontalPosition.pixels, 20.0); + expect(horizontalPosition.maxScrollExtent, double.infinity); + + await tester.tap(find.text('Make rows fixed')); + await tester.pump(); + expect(verticalPosition.pixels, 300.0); + expect(verticalPosition.maxScrollExtent, lessThan(5000)); + expect(horizontalPosition.pixels, 20.0); + expect(horizontalPosition.maxScrollExtent, double.infinity); + + await tester.tap(find.text('Make columns fixed')); + await tester.pump(); + expect(verticalPosition.pixels, 300.0); + expect(verticalPosition.maxScrollExtent, lessThan(5000)); + expect(horizontalPosition.pixels, 20.0); + expect(horizontalPosition.maxScrollExtent, lessThan(4800)); + + await tester.tap(find.text('Make rows infinite')); + await tester.pump(); + expect(verticalPosition.pixels, 300.0); + expect(verticalPosition.maxScrollExtent, double.infinity); + expect(horizontalPosition.pixels, 20.0); + expect(horizontalPosition.maxScrollExtent, lessThan(4800)); + + await tester.tap(find.text('Make columns infinite')); + await tester.pump(); + expect(verticalPosition.pixels, 300.0); + expect(verticalPosition.maxScrollExtent, double.infinity); + expect(horizontalPosition.pixels, 20.0); + expect(horizontalPosition.maxScrollExtent, double.infinity); + }); +} diff --git a/packages/two_dimensional_scrollables/example/test/table_view/merged_table_test.dart b/packages/two_dimensional_scrollables/example/test/table_view/merged_table_test.dart new file mode 100644 index 00000000000..b00c8b1ec87 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/table_view/merged_table_test.dart @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/table_view/merged_table.dart'; + +void main() { + testWidgets('Builds and can scroll', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: MergedTableExample())); + await tester.pump(); + expect(find.text('Red, 500'), findsOneWidget); + + final Finder verticalScrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.down; + } + return false; + }); + final ScrollPosition verticalPosition = + (tester.state(verticalScrollable) as ScrollableState).position; + + final Finder horizontalScrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.right; + } + return false; + }); + final ScrollPosition horizontalPosition = + (tester.state(horizontalScrollable) as ScrollableState).position; + + expect(verticalPosition.pixels, 0.0); + expect(verticalPosition.maxScrollExtent, 5605.0); + expect(horizontalPosition.pixels, 0.0); + expect(horizontalPosition.maxScrollExtent, 200.0); + verticalPosition.jumpTo(300.0); + horizontalPosition.jumpTo(20.0); + await tester.pump(); + expect(verticalPosition.pixels, 300.0); + expect(verticalPosition.maxScrollExtent, 5605.0); + expect(horizontalPosition.pixels, 20.0); + expect(horizontalPosition.maxScrollExtent, 200.0); + }); +} diff --git a/packages/two_dimensional_scrollables/example/test/table_view_example_test.dart b/packages/two_dimensional_scrollables/example/test/table_view/simple_table_test.dart similarity index 89% rename from packages/two_dimensional_scrollables/example/test/table_view_example_test.dart rename to packages/two_dimensional_scrollables/example/test/table_view/simple_table_test.dart index f5cfee64766..0186c4d68ba 100644 --- a/packages/two_dimensional_scrollables/example/test/table_view_example_test.dart +++ b/packages/two_dimensional_scrollables/example/test/table_view/simple_table_test.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:table_view_example/main.dart'; +import 'package:two_dimensional_examples/table_view/simple_table.dart'; void main() { testWidgets('Example app builds & scrolls', (WidgetTester tester) async { - await tester.pumpWidget(const TableExampleApp()); + await tester.pumpWidget(const MaterialApp(home: TableExample())); await tester.pump(); expect(find.text('Jump to Top'), findsOneWidget); @@ -31,7 +31,7 @@ void main() { }); testWidgets('Example app buttons work', (WidgetTester tester) async { - await tester.pumpWidget(const TableExampleApp()); + await tester.pumpWidget(const MaterialApp(home: TableExample())); await tester.pump(); final Finder scrollable = find.byWidgetPredicate((Widget widget) { diff --git a/packages/two_dimensional_scrollables/example/test/table_view/table_explorer_test.dart b/packages/two_dimensional_scrollables/example/test/table_view/table_explorer_test.dart new file mode 100644 index 00000000000..3de3b4204f7 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/table_view/table_explorer_test.dart @@ -0,0 +1,39 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/table_view/infinite_table.dart'; +import 'package:two_dimensional_examples/table_view/merged_table.dart'; +import 'package:two_dimensional_examples/table_view/simple_table.dart'; +import 'package:two_dimensional_examples/table_view/table_explorer.dart'; + +void main() { + testWidgets('Table explorer switches between samples', + (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: TableExplorer())); + await tester.pumpAndSettle(); + // The first example + expect(find.byType(TableExample), findsOneWidget); + expect(find.byType(MergedTableExample), findsNothing); + expect(find.byType(InfiniteTableExample), findsNothing); + expect(find.byType(Radio), findsNWidgets(3)); + final Finder buttons = find.byType(Radio); + await tester.tap(buttons.at(1)); + await tester.pumpAndSettle(); + expect(find.byType(TableExample), findsNothing); + expect(find.byType(MergedTableExample), findsOneWidget); + expect(find.byType(InfiniteTableExample), findsNothing); + await tester.tap(buttons.at(2)); + await tester.pumpAndSettle(); + expect(find.byType(TableExample), findsNothing); + expect(find.byType(MergedTableExample), findsNothing); + expect(find.byType(InfiniteTableExample), findsOneWidget); + await tester.tap(buttons.at(0)); + await tester.pumpAndSettle(); + expect(find.byType(TableExample), findsOneWidget); + expect(find.byType(MergedTableExample), findsNothing); + expect(find.byType(InfiniteTableExample), findsNothing); + }); +} diff --git a/packages/two_dimensional_scrollables/example/test/tree_view/custom_tree_test.dart b/packages/two_dimensional_scrollables/example/test/tree_view/custom_tree_test.dart new file mode 100644 index 00000000000..4146fb3f544 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/tree_view/custom_tree_test.dart @@ -0,0 +1,69 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/tree_view/custom_tree.dart'; + +void main() { + testWidgets('Example builds and can be interacted with', + (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: CustomTreeExample())); + await tester.pumpAndSettle(); + expect(find.text('README.md'), findsOneWidget); + expect(find.text('common'), findsNothing); + await tester.tap(find.byType(Icon).last); + await tester.pumpAndSettle(); + expect(find.text('common'), findsOneWidget); + }); + + testWidgets('Can scroll', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: CustomTreeExample())); + await tester.pumpAndSettle(); + + final Finder verticalScrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.down; + } + return false; + }); + ScrollPosition verticalPosition = + (tester.state(verticalScrollable) as ScrollableState).position; + + expect(verticalPosition.maxScrollExtent, 0.0); + expect(verticalPosition.pixels, 0.0); + + final CustomTreeExampleState state = tester.state( + find.byType(CustomTreeExample), + ) as CustomTreeExampleState; + + state.treeController.toggleNode(state.treeController.getNodeFor('lib')!); + await tester.pumpAndSettle(); + verticalPosition = + (tester.state(verticalScrollable) as ScrollableState).position; + expect(verticalPosition.maxScrollExtent, 0.0); + expect(verticalPosition.pixels, 0.0); + state.treeController.toggleNode(state.treeController.getNodeFor('test')!); + await tester.pumpAndSettle(); + + verticalPosition = + (tester.state(verticalScrollable) as ScrollableState).position; + + expect(verticalPosition.maxScrollExtent, 10.0); + expect(verticalPosition.pixels, 0.0); + state.treeController.toggleNode(state.treeController.getNodeFor('src')!); + await tester.pumpAndSettle(); + + verticalPosition = + (tester.state(verticalScrollable) as ScrollableState).position; + + // Enough nodes expanded to allow us to scroll + expect(verticalPosition.maxScrollExtent, 190.0); + expect(verticalPosition.pixels, 0.0); + state.verticalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(verticalPosition.maxScrollExtent, 190.0); + expect(verticalPosition.pixels, 10.0); + }); +} diff --git a/packages/two_dimensional_scrollables/example/test/tree_view/simple_tree_test.dart b/packages/two_dimensional_scrollables/example/test/tree_view/simple_tree_test.dart new file mode 100644 index 00000000000..6c15316923d --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/tree_view/simple_tree_test.dart @@ -0,0 +1,60 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/tree_view/simple_tree.dart'; + +void main() { + testWidgets('Example builds and can be interacted with', + (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: TreeExample())); + await tester.pumpAndSettle(); + expect( + find.text("It's supercalifragilisticexpialidocious"), + findsOneWidget, + ); + expect( + find.text('Um-dittle-ittl-um-dittle-I'), + findsNothing, + ); + await tester.tap(find.byType(Icon).last); + await tester.pumpAndSettle(); + expect( + find.text('Um-dittle-ittl-um-dittle-I'), + findsOneWidget, + ); + }); + + testWidgets('Can scroll ', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: TreeExample())); + await tester.pumpAndSettle(); + final Finder horizontalScrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.right; + } + return false; + }); + ScrollPosition horizontalPosition = + (tester.state(horizontalScrollable) as ScrollableState).position; + + expect(horizontalPosition.maxScrollExtent, greaterThan(190)); + expect(horizontalPosition.pixels, 0.0); + final TreeExampleState state = tester.state( + find.byType(TreeExample), + ) as TreeExampleState; + + state.treeController.expandAll(); + await tester.pumpAndSettle(); + horizontalPosition = + (tester.state(horizontalScrollable) as ScrollableState).position; + // Expanding all of the node increased the max extent. + expect(horizontalPosition.maxScrollExtent, 502.0); + expect(horizontalPosition.pixels, 0.0); + state.horizontalController.jumpTo(10.0); + await tester.pumpAndSettle(); + expect(horizontalPosition.maxScrollExtent, 502.0); + expect(horizontalPosition.pixels, 10.0); + }); +} diff --git a/packages/two_dimensional_scrollables/example/test/tree_view/tree_explorer_test.dart b/packages/two_dimensional_scrollables/example/test/tree_view/tree_explorer_test.dart new file mode 100644 index 00000000000..d9be8e93cc6 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/tree_view/tree_explorer_test.dart @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_examples/tree_view/custom_tree.dart'; +import 'package:two_dimensional_examples/tree_view/simple_tree.dart'; +import 'package:two_dimensional_examples/tree_view/tree_explorer.dart'; + +void main() { + testWidgets('Tree explorer switches between samples', + (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: TreeExplorer())); + await tester.pumpAndSettle(); + // The first example + expect(find.byType(TreeExample), findsOneWidget); + expect(find.byType(CustomTreeExample), findsNothing); + expect(find.byType(Radio), findsNWidgets(2)); + await tester.tap(find.byType(Radio).last); + await tester.pumpAndSettle(); + expect(find.byType(TreeExample), findsNothing); + expect(find.byType(CustomTreeExample), findsOneWidget); + await tester.tap(find.byType(Radio).first); + await tester.pumpAndSettle(); + expect(find.byType(TreeExample), findsOneWidget); + expect(find.byType(CustomTreeExample), findsNothing); + }); +} diff --git a/packages/two_dimensional_scrollables/lib/src/common/span.dart b/packages/two_dimensional_scrollables/lib/src/common/span.dart index d2e88fc4aca..fcf4e0070e3 100644 --- a/packages/two_dimensional_scrollables/lib/src/common/span.dart +++ b/packages/two_dimensional_scrollables/lib/src/common/span.dart @@ -63,6 +63,30 @@ class Span { this.foregroundDecoration, }) : padding = padding ?? const SpanPadding(); + /// Create a clone of the current [Span] but with provided + /// parameters overridden. + Span copyWith({ + SpanExtent? extent, + SpanPadding? padding, + Map? recognizerFactories, + PointerEnterEventListener? onEnter, + PointerExitEventListener? onExit, + MouseCursor? cursor, + SpanDecoration? backgroundDecoration, + SpanDecoration? foregroundDecoration, + }) { + return Span( + extent: extent ?? this.extent, + padding: padding ?? this.padding, + recognizerFactories: recognizerFactories ?? this.recognizerFactories, + onEnter: onEnter ?? this.onEnter, + onExit: onExit ?? this.onExit, + cursor: cursor ?? this.cursor, + backgroundDecoration: backgroundDecoration ?? this.backgroundDecoration, + foregroundDecoration: foregroundDecoration ?? this.foregroundDecoration, + ); + } + /// Defines the extent of the span. /// /// If the span represents a row, this is the height of the row. If it diff --git a/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart b/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart new file mode 100644 index 00000000000..ab00f7cf7fa --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart @@ -0,0 +1,728 @@ +// 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:collection' show LinkedHashMap; +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'tree_core.dart'; +import 'tree_delegate.dart'; +import 'tree_span.dart'; + +// Used during paint to delineate animating portions of the tree. +typedef _PaintSegment = ({int leadingIndex, int trailingIndex}); + +/// A render object for viewing [RenderBox]es in a tree format that extends in +/// both the horizontal and vertical dimensions. +/// +/// [RenderTreeViewport] is the visual workhorse of the [TreeView]. It +/// displays a subset of its [TreeViewNode] rows according to its own dimensions +/// and the given [verticalOffset] and [horizontalOffset]. As the offset varies, +/// different nodes are visible through the viewport. +class RenderTreeViewport extends RenderTwoDimensionalViewport { + /// Creates a viewport for [RenderBox] objects in a tree format of rows. + RenderTreeViewport({ + required Map activeAnimations, + required Map rowDepths, + required double indentation, + required super.horizontalOffset, + required super.horizontalAxisDirection, + required super.verticalOffset, + required super.verticalAxisDirection, + required TreeRowDelegateMixin super.delegate, + required super.childManager, + super.cacheExtent, + super.clipBehavior, + }) : _activeAnimations = activeAnimations, + _rowDepths = rowDepths, + _indentation = indentation, + assert(indentation >= 0), + assert(verticalAxisDirection == AxisDirection.down && + horizontalAxisDirection == AxisDirection.right), + // This is fixed as there is currently only one traversal pattern, https://github.com/flutter/flutter/issues/148357 + super(mainAxis: Axis.vertical); + + @override + TreeRowDelegateMixin get delegate => super.delegate as TreeRowDelegateMixin; + @override + set delegate(TreeRowDelegateMixin value) { + super.delegate = value; + } + + /// The currently active [TreeViewNode] animations. + /// + /// Since the index of animating nodes can change at any time, the unique key + /// is used to track an animation of nodes across frames. + Map get activeAnimations { + return _activeAnimations; + } + + Map _activeAnimations; + set activeAnimations(Map value) { + if (_activeAnimations == value) { + return; + } + _activeAnimations = value; + markNeedsLayout(withDelegateRebuild: true); + } + + /// The depth of each currently active node in the tree. + /// + /// This is used to properly set the [TreeVicinity]. + Map get rowDepths => _rowDepths; + Map _rowDepths; + set rowDepths(Map value) { + if (_rowDepths == value) { + return; + } + _rowDepths = value; + markNeedsLayout(); + } + + /// The number of pixels by which child nodes will be offset in the cross axis + /// based on [rowDepths]. + /// + /// If zero, children can alternatively be offset in [TreeView.treeRowBuilder] + /// for more options to customize the indented space. + double get indentation => _indentation; + double _indentation; + set indentation(double value) { + if (_indentation == value) { + return; + } + assert(indentation >= 0.0); + _indentation = value; + markNeedsLayout(); + } + + // Cached metrics + Map _rowMetrics = {}; + int? _firstRow; + int? _lastRow; + double _furthestHorizontalExtent = 0.0; + // How far rows should be laid out in a given frame. + double get _targetRowPixel { + return cacheExtent + verticalOffset.pixels + viewportDimension.height; + } + + // Whether or not there is visual overflow in the viewport. + bool get _hasVisualOverflow => _verticalOverflows || _horizontalOverflows; + bool _verticalOverflows = false; + bool _horizontalOverflows = false; + + // Since the index of animating children can change at anytime, we use a + // UniqueKey to track them during the lifetime of the animation. + // Maps the index of parents to the animation key of their children. + final Map _animationLeadingIndices = {}; + // Maps the key of child node animations to the fixed distance they are + // traversing during the animation. Determined at the start of the animation. + final Map _animationOffsets = {}; + // Updates the cache at the start of eah layout pass. + void _updateAnimationCache() { + _animationLeadingIndices.clear(); + _activeAnimations.forEach( + (UniqueKey key, TreeViewNodesAnimation animation) { + _animationLeadingIndices[animation.fromIndex] = key; + }, + ); + // Remove any stored offsets or clip layers that are no longer actively + // animating. + _animationOffsets.removeWhere((UniqueKey key, _) { + return !_activeAnimations.keys.contains(key); + }); + _clipHandles.removeWhere( + (UniqueKey key, LayerHandle handle) { + if (!_activeAnimations.keys.contains(key)) { + handle.layer = null; + return true; + } + return false; + }, + ); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? row = firstChild; + while (row != null) { + final TwoDimensionalViewportParentData parentData = parentDataOf(row); + if (!parentData.isVisible) { + // This row is not visible, so it cannot be hit. + row = childAfter(row); + continue; + } + final Rect rowRect = parentData.paintOffset! & + Size(viewportDimension.width, row.size.height); + if (rowRect.contains(position)) { + result.addWithPaintOffset( + offset: parentData.paintOffset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - parentData.paintOffset!); + return row!.hitTest(result, position: transformed); + }, + ); + result.add( + HitTestEntry(_rowMetrics[parentData.vicinity.yIndex]!), + ); + return true; + } + row = childAfter(row); + } + return false; + } + + @override + void dispose() { + _clipHandles.removeWhere( + (UniqueKey key, LayerHandle handle) { + handle.layer = null; + return true; + }, + ); + for (final _Span span in _rowMetrics.values) { + span.dispose(); + } + super.dispose(); + } + + void _computeAnimationOffsetFor(UniqueKey key, double position) { + // `position` represents the trailing edge of the parent node that initiated + // the animation. + assert(_activeAnimations[key] != null); + double currentPosition = position; + final int startingIndex = _activeAnimations[key]!.fromIndex; + final int lastIndex = _activeAnimations[key]!.toIndex; + int currentIndex = startingIndex; + double totalAnimatingOffset = 0.0; + // We animate only a portion of children that would be visible/in the cache + // extent, unless all animating children would fit on the screen. + while (currentIndex <= lastIndex && currentPosition < _targetRowPixel) { + _Span? span = _rowMetrics.remove(currentIndex); + assert(needsDelegateRebuild || span != null); + final TreeRow configuration = needsDelegateRebuild + ? delegate.buildRow(TreeVicinity( + depth: _rowDepths[currentIndex]!, + row: currentIndex, + )) + : span!.configuration; + span ??= _Span(); + final double extent = configuration.extent.calculateExtent( + TreeRowExtentDelegate( + viewportExtent: viewportDimension.height, + precedingExtent: position, + ), + ); + totalAnimatingOffset += extent; + currentPosition += extent; + currentIndex++; + } + // For the life of this animation, which affects all children following + // startingIndex (regardless of if they are a child of the triggering + // parent), they will be offset by totalAnimatingOffset * the + // animation value. This is because even though more children can be + // scrolled into view, the same distance must be maintained for a smooth + // animation. + _animationOffsets[key] = totalAnimatingOffset; + } + + void _updateRowMetrics() { + assert(needsDelegateRebuild || didResize); + _firstRow = null; + _lastRow = null; + double totalAnimationOffset = 0.0; + double startOfRow = 0; + final Map newRowMetrics = {}; + for (int row = 0; row < delegate.rowCount; row++) { + final double leadingOffset = startOfRow; + _Span? span = _rowMetrics.remove(row); + assert(needsDelegateRebuild || span != null); + final TreeRow configuration = needsDelegateRebuild + ? delegate.buildRow(TreeVicinity( + depth: _rowDepths[row]!, + row: row, + )) + : span!.configuration; + span ??= _Span(); + final double extent = configuration.extent.calculateExtent( + TreeRowExtentDelegate( + viewportExtent: viewportDimension.height, + precedingExtent: leadingOffset, + ), + ); + if (_animationLeadingIndices.keys.contains(row)) { + final UniqueKey animationKey = _animationLeadingIndices[row]!; + if (_animationOffsets[animationKey] == null) { + // We have not computed the distance this block is traversing over the + // lifetime of the animation. + _computeAnimationOffsetFor(animationKey, startOfRow); + } + // We add the offset accounting for the animation value. + totalAnimationOffset += _animationOffsets[animationKey]! * + (1 - _activeAnimations[animationKey]!.value); + } + span.update( + configuration: configuration, + leadingOffset: leadingOffset, + extent: extent, + animationOffset: totalAnimationOffset, + ); + newRowMetrics[row] = span; + if (span.trailingOffset >= verticalOffset.pixels && _firstRow == null) { + _firstRow = row; + } + if (span.trailingOffset - totalAnimationOffset >= _targetRowPixel && + _lastRow == null) { + _lastRow = row; + } + startOfRow = span.trailingOffset; + totalAnimationOffset = 0.0; + } + for (final _Span span in _rowMetrics.values) { + span.dispose(); + } + _rowMetrics = newRowMetrics; + if (_firstRow != null) { + _lastRow ??= _rowMetrics.length - 1; + } + } + + void _updateFirstAndLastVisibleRow() { + _firstRow = null; + _lastRow = null; + for (int row = 0; row < _rowMetrics.length; row++) { + final double endOfRow = _rowMetrics[row]!.trailingOffset; + if (endOfRow >= verticalOffset.pixels && _firstRow == null) { + _firstRow = row; + } + if (endOfRow >= _targetRowPixel && _lastRow == null) { + _lastRow = row; + break; + } + } + if (_firstRow != null) { + _lastRow ??= _rowMetrics.length - 1; + } + } + + void _updateScrollBounds() { + final double maxHorizontalExtent = math.max( + 0.0, + _furthestHorizontalExtent - viewportDimension.width, + ); + _horizontalOverflows = maxHorizontalExtent > 0.0; + + final double maxVerticalExtent = math.max( + 0.0, + _rowMetrics[_lastRow!]!.trailingOffset - viewportDimension.height, + ); + _verticalOverflows = maxVerticalExtent > 0.0; + + final bool acceptedDimension = horizontalOffset.applyContentDimensions( + 0.0, + maxHorizontalExtent, + ) && + verticalOffset.applyContentDimensions( + 0.0, + maxVerticalExtent, + ); + + if (!acceptedDimension) { + _updateFirstAndLastVisibleRow(); + } + } + + @override + void layoutChildSequence() { + _updateAnimationCache(); + if (needsDelegateRebuild || didResize) { + // Recomputes the tree row metrics, invalidates any cached information. + _furthestHorizontalExtent = 0.0; + _updateRowMetrics(); + } else { + // Updates the visible rows based on cached _rowMetrics. + _updateFirstAndLastVisibleRow(); + } + + if (_firstRow == null) { + assert(_lastRow == null); + return; + } + assert(_firstRow != null && _lastRow != null); + + _Span rowSpan; + double rowOffset = + -verticalOffset.pixels + _rowMetrics[_firstRow!]!.leadingOffset; + for (int row = _firstRow!; row <= _lastRow!; row++) { + rowSpan = _rowMetrics[row]!; + final double rowHeight = rowSpan.extent; + if (_animationLeadingIndices.keys.contains(row)) { + rowOffset -= rowSpan.animationOffset; + } + rowOffset += rowSpan.configuration.padding.leading; + + final TreeVicinity vicinity = TreeVicinity( + depth: _rowDepths[row]!, + row: row, + ); + final RenderBox child = buildOrObtainChildFor(vicinity)!; + final TwoDimensionalViewportParentData parentData = parentDataOf(child); + final BoxConstraints childConstraints = BoxConstraints( + minHeight: rowHeight, + maxHeight: rowHeight, + // Width is allowed to be unbounded. + ); + child.layout(childConstraints, parentUsesSize: true); + parentData.layoutOffset = Offset( + (_rowDepths[row]! * indentation) - horizontalOffset.pixels, + rowOffset, + ); + rowOffset += rowHeight + rowSpan.configuration.padding.trailing; + _furthestHorizontalExtent = math.max( + parentData.layoutOffset!.dx + child.size.width, + _furthestHorizontalExtent, + ); + } + _updateScrollBounds(); + } + + // Maps the UniqueKey associated with animating node segments with the clip + // LayerHandle. + final Map> _clipHandles = + >{}; + // Used as the UniqueKey for the viewport or leading segment that does not + // have an animation key. When we are not animating, this clips the viewport + // bounds if there is visual overflow. When we are animating, it clips the + // leading segment if there is visual overflow. + final UniqueKey _viewportClipKey = UniqueKey(); + + @override + void paint(PaintingContext context, Offset offset) { + if (_firstRow == null) { + assert(_lastRow == null); + return; + } + assert(_firstRow != null && _lastRow != null); + + if (_animationLeadingIndices.isEmpty) { + // There are no animations running. Clip only if there is visual overflow. + if (_hasVisualOverflow && clipBehavior != Clip.none) { + _clipHandles[_viewportClipKey] ??= LayerHandle(); + _clipHandles[_viewportClipKey]!.layer = context.pushClipRect( + needsCompositing, + offset, + Offset.zero & size, + (PaintingContext context, Offset offset) { + _paintRows( + context, + offset, + leadingRow: _firstRow!, + trailingRow: _lastRow!, + ); + }, + clipBehavior: clipBehavior, + oldLayer: _clipHandles[_viewportClipKey]!.layer, + ); + } else { + _clipHandles[_viewportClipKey]?.layer = null; + _paintRows( + context, + offset, + leadingRow: _firstRow!, + trailingRow: _lastRow!, + ); + } + return; + } + + // We are animating. + // Separate animating segments to clip for any overlap. + int leadingIndex = _firstRow!; + final List animationIndices = _animationLeadingIndices.keys.toList() + ..sort(); + final List<_PaintSegment> paintSegments = <_PaintSegment>[]; + while (animationIndices.isNotEmpty) { + final int trailingIndex = animationIndices.removeAt(0); + paintSegments.add(( + leadingIndex: leadingIndex, + trailingIndex: trailingIndex - 1, + )); + leadingIndex = trailingIndex; + } + paintSegments.add((leadingIndex: leadingIndex, trailingIndex: _lastRow!)); + + // Paint, clipping for all but the first segment, unless there is visual + // overflow. + final _PaintSegment firstSegment = paintSegments.removeAt(0); + if (_hasVisualOverflow && clipBehavior != Clip.none) { + _clipHandles[_viewportClipKey] ??= LayerHandle(); + _clipHandles[_viewportClipKey]!.layer = context.pushClipRect( + needsCompositing, + offset, + Offset.zero & size, + (PaintingContext context, Offset offset) { + _paintRows( + context, + offset, + leadingRow: firstSegment.leadingIndex, + trailingRow: firstSegment.trailingIndex, + ); + }, + clipBehavior: clipBehavior, + oldLayer: _clipHandles[_viewportClipKey]!.layer, + ); + } else { + _clipHandles[_viewportClipKey]?.layer = null; + _paintRows( + context, + offset, + leadingRow: firstSegment.leadingIndex, + trailingRow: firstSegment.trailingIndex, + ); + } + // Paint the rest with clip layers. + while (paintSegments.isNotEmpty) { + final _PaintSegment segment = paintSegments.removeAt(0); + final int parentIndex = segment.leadingIndex - 1; + final double leadingOffset = _rowMetrics[parentIndex]!.trailingOffset; + final double trailingOffset = + _rowMetrics[segment.trailingIndex]!.trailingOffset; + final Rect rect = Rect.fromPoints( + Offset(0.0, leadingOffset - verticalOffset.pixels), + Offset( + viewportDimension.width, + math.min( + trailingOffset - verticalOffset.pixels, + viewportDimension.height, + ), + ), + ); + // We use the same animation key to keep track of the clip layer, unless + // this is the odd man out segment. + final UniqueKey key = _animationLeadingIndices[leadingIndex]!; + _clipHandles[key] ??= LayerHandle(); + _clipHandles[key]!.layer = context.pushClipRect( + needsCompositing, + offset, + rect, + (PaintingContext context, Offset offset) { + _paintRows( + context, + offset, + leadingRow: segment.leadingIndex, + trailingRow: segment.trailingIndex, + ); + }, + oldLayer: _clipHandles[key]!.layer, + ); + } + } + + void _paintRows( + PaintingContext context, + Offset offset, { + required int leadingRow, + required int trailingRow, + }) { + // Row decorations + final LinkedHashMap foregroundRows = + LinkedHashMap(); + final LinkedHashMap backgroundRows = + LinkedHashMap(); + + int currentRow = leadingRow; + while (currentRow <= trailingRow) { + final _Span rowSpan = _rowMetrics[currentRow]!; + final TreeRow configuration = rowSpan.configuration; + if (configuration.backgroundDecoration != null || + configuration.foregroundDecoration != null) { + final RenderBox child = getChildFor( + TreeVicinity(depth: _rowDepths[currentRow]!, row: currentRow), + )!; + + Rect getRowRect(bool consumePadding) { + final TwoDimensionalViewportParentData parentData = + parentDataOf(child); + // Decoration rects cover the whole row from the left and right + // edge of the viewport. + return Rect.fromPoints( + Offset(0.0, parentData.layoutOffset!.dy), + Offset( + viewportDimension.width, + rowSpan.trailingOffset - verticalOffset.pixels, + ), + ); + } + + if (configuration.backgroundDecoration != null) { + final Rect rect = getRowRect( + configuration.backgroundDecoration!.consumeSpanPadding, + ); + backgroundRows[rect] = configuration.backgroundDecoration!; + } + if (configuration.foregroundDecoration != null) { + final Rect rect = getRowRect( + configuration.foregroundDecoration!.consumeSpanPadding, + ); + foregroundRows[rect] = configuration.foregroundDecoration!; + } + } + currentRow++; + } + + // Get to painting. + // Background decorations first. + backgroundRows.forEach((Rect rect, TreeRowDecoration decoration) { + final TreeRowDecorationPaintDetails paintingDetails = + TreeRowDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: horizontalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + // Child nodes. + for (int row = leadingRow; row <= trailingRow; row++) { + final RenderBox child = getChildFor( + TreeVicinity(depth: _rowDepths[row]!, row: row), + )!; + final TwoDimensionalViewportParentData rowParentData = + parentDataOf(child); + if (rowParentData.isVisible) { + context.paintChild(child, offset + rowParentData.paintOffset!); + } + } + // Foreground decorations. + foregroundRows.forEach((Rect rect, TreeRowDecoration decoration) { + final TreeRowDecorationPaintDetails paintingDetails = + TreeRowDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: horizontalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + } +} + +class _Span + with Diagnosticable + implements HitTestTarget, MouseTrackerAnnotation { + double get leadingOffset => _leadingOffset; + late double _leadingOffset; + + double get extent => _extent; + late double _extent; + + TreeRow get configuration => _configuration!; + TreeRow? _configuration; + + double get animationOffset => _animationOffset; + late double _animationOffset; + + double get trailingOffset { + return leadingOffset + + extent + + configuration.padding.leading + + configuration.padding.trailing; + } + + // ---- Span Management ---- + + void update({ + required TreeRow configuration, + required double leadingOffset, + required double extent, + required double animationOffset, + }) { + _leadingOffset = leadingOffset; + _extent = extent; + _animationOffset = animationOffset; + if (configuration == _configuration) { + return; + } + _configuration = configuration; + // Only sync recognizers if they are in use already. + if (_recognizers != null) { + _syncRecognizers(); + } + } + + void dispose() { + _disposeRecognizers(); + } + + // ---- Recognizers management ---- + + Map? _recognizers; + + void _syncRecognizers() { + if (configuration.recognizerFactories.isEmpty) { + _disposeRecognizers(); + return; + } + final Map newRecognizers = + {}; + for (final Type type in configuration.recognizerFactories.keys) { + assert(!newRecognizers.containsKey(type)); + newRecognizers[type] = _recognizers?.remove(type) ?? + configuration.recognizerFactories[type]!.constructor(); + assert( + newRecognizers[type].runtimeType == type, + 'GestureRecognizerFactory of type $type created a GestureRecognizer of ' + 'type ${newRecognizers[type].runtimeType}. The ' + 'GestureRecognizerFactory must be specialized with the type of the ' + 'class that it returns from its constructor method.', + ); + configuration.recognizerFactories[type]! + .initializer(newRecognizers[type]!); + } + _disposeRecognizers(); // only disposes the ones that where not re-used above. + _recognizers = newRecognizers; + } + + void _disposeRecognizers() { + if (_recognizers != null) { + for (final GestureRecognizer recognizer in _recognizers!.values) { + recognizer.dispose(); + } + _recognizers = null; + } + } + + // ---- HitTestTarget ---- + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent && + configuration.recognizerFactories.isNotEmpty) { + if (_recognizers == null) { + _syncRecognizers(); + } + assert(_recognizers != null); + for (final GestureRecognizer recognizer in _recognizers!.values) { + recognizer.addPointer(event); + } + } + } + + // ---- MouseTrackerAnnotation ---- + + @override + MouseCursor get cursor => configuration.cursor; + + @override + PointerEnterEventListener? get onEnter => configuration.onEnter; + + @override + PointerExitEventListener? get onExit => configuration.onExit; + + @override + bool get validForMouseTracker => true; +} diff --git a/packages/two_dimensional_scrollables/lib/src/tree_view/tree.dart b/packages/two_dimensional_scrollables/lib/src/tree_view/tree.dart new file mode 100644 index 00000000000..ba6c4632c40 --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/tree_view/tree.dart @@ -0,0 +1,1077 @@ +// 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/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +import 'render_tree.dart'; +import 'tree_core.dart'; +import 'tree_delegate.dart'; +import 'tree_span.dart'; + +// The classes in these files follow the same pattern as the one dimensional +// sliver tree in the framework. +// +// After rolling to stable, these classes may be deprecated, or more likely +// made to be typedefs/subclasses of the framework core tree components. They +// could also live on if at a later date the 2D TreeView deviates or adds +// special features not relevant to the 1D sliver components of the framework. + +const double _kDefaultRowExtent = 40.0; + +/// A data structure for configuring children of a [TreeView]. +/// +/// A [TreeViewNode.content] can be of any type [T], but must correspond with +/// the same type of the [TreeView]. +/// +/// Getters for [depth], [parent] and [isExpanded] are managed by the +/// [TreeView]'s state. +class TreeViewNode { + /// Creates a [TreeViewNode] instance for use in a [TreeView]. + TreeViewNode( + T content, { + List>? children, + bool expanded = false, + }) : _expanded = children != null && children.isNotEmpty && expanded, + _content = content, + _children = children ?? >[]; + + /// The subject matter of the node. + /// + /// Must correspond with the type of [TreeView]. + T get content => _content; + final T _content; + + /// Other [TreeViewNode]s that this node will be [parent] to. + /// + /// Modifying the children of nodes in a [TreeView] will cause the tree to be + /// rebuilt so that newly added active nodes are reflected in the tree. + List> get children => _children; + final List> _children; + + /// Whether or not this node is expanded in the tree. + /// + /// Cannot be expanded if there are no children. + bool get isExpanded => _expanded; + bool _expanded; + + /// The number of parent nodes between this node and the root of the tree. + int? get depth => _depth; + int? _depth; + + /// The parent [TreeViewNode] of this node. + TreeViewNode? get parent => _parent; + TreeViewNode? _parent; + + @override + String toString() { + return 'TreeViewNode: $content, depth: ${depth == 0 ? 'root' : depth}, ' + '${children.isEmpty ? 'leaf' : 'parent, expanded: $isExpanded'}'; + } +} + +/// Enables control over the [TreeViewNodes] of a [TreeView]. +/// +/// It can be useful to expand or collapse nodes of the tree +/// programmatically, for example to reconfigure an existing node +/// based on a system event. To do so, create a [TreeView] +/// with a [TreeViewController] that's owned by a stateful widget +/// or look up the tree's automatically created [TreeViewController] +/// with [TreeViewController.of] +/// +/// The controller's methods to expand or collapse nodes cause the +/// the [TreeView] to rebuild, so they may not be called from +/// a build method. +class TreeViewController { + /// Create a controller to be used with [TreeView.controller]. + TreeViewController(); + + TreeViewStateMixin? _state; + + /// Whether the given [TreeViewNode] built with this controller is in an + /// expanded state. + /// + /// See also: + /// + /// * [expandNode], which expands a given [TreeViewNode]. + /// * [collapseNode], which collapses a given [TreeViewNode]. + /// * [TreeView.controller] to create an TreeView with a controller. + bool isExpanded(TreeViewNode node) { + assert(_state != null); + return _state!.isExpanded(node); + } + + /// Whether or not the given [TreeViewNode] is enclosed within its parent + /// [TreeViewNode]. + /// + /// If the [TreeViewNode.parent] [isExpanded], or this is a root node, the given + /// node is active and this method will return true. This does not reflect + /// whether or not the node is visible in the [Viewport]. + bool isActive(TreeViewNode node) { + assert(_state != null); + return _state!.isActive(node); + } + + /// Returns the [TreeViewNode] containing the associated content, if it exists. + /// + /// If no node exists, this will return null. This does not reflect whether + /// or not a node [isActive], or if it is currently visible in the viewport. + TreeViewNode? getNodeFor(Object? content) { + assert(_state != null); + return _state!.getNodeFor(content); + } + + /// Switches the given [TreeViewNode]s expanded state. + /// + /// May trigger an animation to reveal or hide the node's children based on + /// the [TreeView.toggleAnimationStyle]. + /// + /// If the node does not have any children, nothing will happen. + void toggleNode(TreeViewNode node) { + assert(_state != null); + return _state!.toggleNode(node); + } + + /// Expands the [TreeViewNode] that was built with this controller. + /// + /// If the node is already in the expanded state (see [isExpanded]), calling + /// this method has no effect. + /// + /// Calling this method may cause the [TreeView] to rebuild, so it may + /// not be called from a build method. + /// + /// Calling this method will trigger the [TreeView.onNodeToggle] callback. + /// + /// See also: + /// + /// * [collapseNode], which collapses the [TreeViewNode]. + /// * [isExpanded] to check whether the tile is expanded. + /// * [TreeView.controller] to create an TreeView with a controller. + void expandNode(TreeViewNode node) { + assert(_state != null); + if (!node.isExpanded) { + _state!.toggleNode(node); + } + } + + /// Expands all parent [TreeViewNode]s in the tree. + void expandAll() { + assert(_state != null); + _state!.expandAll(); + } + + /// Closes all parent [TreeViewNode]s in the tree. + void collapseAll() { + assert(_state != null); + _state!.collapseAll(); + } + + /// Returns the current row index of the given [TreeViewNode]. + /// + /// If the node is not currently active in the tree, meaning its parent is + /// collapsed, this will return null. + int? getActiveIndexFor(TreeViewNode node) { + assert(_state != null); + return _state!.getActiveIndexFor(node); + } + + /// Collapses the [TreeViewNode] that was built with this controller. + /// + /// If the node is already in the collapsed state (see [isExpanded]), calling + /// this method has no effect. + /// + /// Calling this method may cause the [TreeView] to rebuild, so it may + /// not be called from a build method. + /// + /// Calling this method will trigger the [TreeView.onNodeToggle] callback. + /// + /// See also: + /// + /// * [expandNode], which expands the tile. + /// * [isExpanded] to check whether the tile is expanded. + /// * [TreeView.controller] to create an TreeView with a controller. + void collapseNode(TreeViewNode node) { + assert(_state != null); + if (node.isExpanded) { + _state!.toggleNode(node); + } + } + + /// Finds the [TreeViewController] for the closest [TreeView] instance + /// that encloses the given context. + /// + /// If no [TreeView] encloses the given context, calling this + /// method will cause an assert in debug mode, and throw an + /// exception in release mode. + /// + /// To return null if there is no [TreeView] use [maybeOf] instead. + /// + /// Typical usage of the [TreeViewController.of] function is to call it + /// from within the `build` method of a descendant of an [TreeView]. + /// + /// When the [TreeView] is actually created in the same `build` + /// function as the callback that refers to the controller, then the + /// `context` argument to the `build` function can't be used to find + /// the [TreeViewController] (since it's "above" the widget + /// being returned in the widget tree). In cases like that you can + /// add a [Builder] widget, which provides a new scope with a + /// [BuildContext] that is "under" the [TreeView]. + static TreeViewController of(BuildContext context) { + final _TreeViewState? result = + context.findAncestorStateOfType<_TreeViewState>(); + if (result != null) { + return result.controller; + } + throw FlutterError.fromParts([ + ErrorSummary( + 'TreeViewController.of() called with a context that does not contain a ' + 'TreeView.', + ), + ErrorDescription( + 'No TreeView ancestor could be found starting from the context that ' + 'was passed to TreeViewController.of(). ' + 'This usually happens when the context provided is from the same ' + 'StatefulWidget as that whose build function actually creates the ' + 'TreeView widget being sought.', + ), + ErrorHint( + 'There are several ways to avoid this problem. The simplest is to use ' + 'a Builder to get a context that is "under" the TreeView.', + ), + ErrorHint( + 'A more efficient solution is to split your build function into ' + 'several widgets. This introduces a new context from which you can ' + 'obtain the TreeView. In this solution, you would have an outer ' + 'widget that creates the TreeView populated by instances of your new ' + 'inner widgets, and then in these inner widgets you would use ' + 'TreeViewController.of().', + ), + context.describeElement('The context used was'), + ]); + } + + /// Finds the [TreeView] from the closest instance of this class that + /// encloses the given context and returns its [TreeViewController]. + /// + /// If no [TreeView] encloses the given context then return null. + /// To throw an exception instead, use [of] instead of this function. + /// + /// See also: + /// + /// * [of], a similar function to this one that throws if no [TreeView] + /// encloses the given context. Also includes some sample code in its + /// documentation. + static TreeViewController? maybeOf(BuildContext context) { + return context + .findAncestorStateOfType<_TreeViewState>() + ?.controller; + } +} + +// END of shared surfaces from the framework. + +/// A widget that displays [TreeViewNode]s that expand and collapse in a +/// vertically and horizontally scrolling [TreeViewport]. +/// +/// The type [T] correlates to the type of [TreeView] and [TreeViewNode], +/// representing the type of [TreeViewNode.content]. +/// +/// The rows of the tree are laid out on demand by the [TreeViewport]'s render +/// object, using [TreeView.treeNodeBuilder]. This will only be called for the +/// nodes that are visible, or within the [TreeViewport.cacheExtent]. +/// +/// The [TreeView.treeNodeBuilder] returns the [Widget] that represents the +/// given [TreeViewNode]. +/// +/// The [TreeView.treeRowBuilder] returns a [TreeRow], +/// which provides details about the row such as the [TreeRowExtent], as well as +/// any [TreeRow.recognizerFactories], [TreeRowDecoration]s, and more. +/// +/// Providing a [TreeController] will enable querying and controlling the state +/// of nodes in the tree. +/// +/// Each active node of the tree will have a [TreeVicinity], representing the +/// resolved row index of the node, based on what nodes are active, as well as +/// the depth. +/// +/// A [TreeView] only supports a vertical axis direction of +/// [AxisDirection.down] and a horizontal axis direction of +/// [AxisDirection.right]. +class TreeView extends StatefulWidget { + /// Creates an instance of a [TreeView] for displaying [TreeViewNode]s + /// that animate expanding and collapsing of nodes. + TreeView({ + super.key, + required this.tree, + this.treeNodeBuilder = TreeView.defaultTreeNodeBuilder, + this.treeRowBuilder = TreeView.defaultTreeRowBuilder, + this.controller, + this.onNodeToggle, + this.toggleAnimationStyle, + this.indentation = TreeViewIndentationType.standard, + this.primary, + this.mainAxis = Axis.vertical, + this.verticalDetails = const ScrollableDetails.vertical(), + this.horizontalDetails = const ScrollableDetails.horizontal(), + this.cacheExtent, + this.diagonalDragBehavior = DiagonalDragBehavior.none, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.clipBehavior = Clip.hardEdge, + this.addAutomaticKeepAlives = true, + this.addRepaintBoundaries = true, + }) : assert(verticalDetails.direction == AxisDirection.down && + horizontalDetails.direction == AxisDirection.right); + + /// The list of [TreeViewNode]s that may be displayed in the [TreeView]. + /// + /// Beyond root nodes, or those in this list, whether or not a given + /// [TreeViewNode] is displayed depends on the [TreeViewNode.isExpanded] value + /// of its parent. The [TreeView] will set the [TreeViewNode.parent] and + /// [TreeViewNode.depth] as nodes are built on demand to ensure the integrity + /// of the tree. + final List> tree; + + /// Called to build an entry of the [TreeView] for the given [TreeViewNode]. + /// + /// By default, if this is unset, the [TreeView.defaultTreeNodeBuilder] is + /// used. + final TreeViewNodeBuilder treeNodeBuilder; + + /// Builds the [TreeRow] that describes the row for the provided + /// [TreeViewNode]. + /// + /// By default, if this is unset, the [TreeView.defaultTreeRowBuilder] + /// is used. + final TreeViewRowBuilder treeRowBuilder; + + /// If provided, the controller can be used to expand and collapse + /// [TreeViewNode]s, or lookup information about the current state of the + /// [TreeView]. + final TreeViewController? controller; + + /// Called when a [TreeViewNode] is toggled to expand or collapse. + /// + /// This will be called before the collapse or expand animation starts, but + /// after the [TreeViewNode.isExpanded] value is updated. This means that + /// [TreeViewNode.isExpanded] will reflect the value it is transitioning to as + /// a result of being toggled. + /// + /// This will not be called if a [TreeViewNode] does not have any children. + final TreeViewNodeCallback? onNodeToggle; + + /// The default [AnimationStyle] for expanding and collapsing nodes in the + /// [TreeView]. + /// + /// The default [AnimationStyle.duration] uses + /// [TreeView.defaultAnimationDuration], which is 150 milliseconds. + /// + /// The default [AnimationStyle.curve] uses [TreeView.defaultAnimationCurve], + /// which is [Curves.linear]. + /// + /// To disable the tree animation, use [AnimationStyle.noAnimation]. + final AnimationStyle? toggleAnimationStyle; + + /// The number of pixels children will be offset by in the cross axis based on + /// their [TreeViewNode.depth]. + /// + /// By default, the indentation is handled by [RenderTreeViewport]. Child + /// nodes are offset by the indentation specified by + /// [TreeViewIndentationType.value] in the cross axis of the viewport. This + /// means the space allotted to the indentation will not be part of the space + /// made available to the Widget returned by [TreeView.treeNodeBuilder]. + /// + /// Alternatively, the indentation can be implemented in + /// [TreeView.treeNodeBuilder]. By providing [TreeViewIndentationType.none], + /// the depth of the given tree row can be accessed + /// in [TreeView.treeNodeBuilder] through [TreeViewNode.depth]. This allows + /// for more customization in building tree rows, such as filling the indented + /// area with decorations or ink effects. + final TreeViewIndentationType indentation; + + /// Whether this is the primary scroll view associated with the parent + /// [PrimaryScrollController]. + /// + /// When this is true, the scroll view is scrollable even if it does not have + /// sufficient content to actually scroll. Otherwise, by default the user can + /// only scroll the view if it has sufficient content. + /// + /// See also: + /// + /// * [TwoDimensionalScrollView.primary], whether or not the + /// [TreeView.mainAxis] will use the [PrimaryScrollController]. + final bool? primary; + + /// The main axis of the two. + /// + /// Used to determine how to apply [primary] when true. This will not affect + /// paint order or traversal order of [TreeViewNode]s. Nodes will be painted + /// in the order they are laid out in the vertical axis. For tree traversal, + /// see [TreeViewTraversalOrder]. + /// + /// Defaults to [Axis.vertical]. + final Axis mainAxis; + + /// The configuration of the vertical Scrollable. + /// + /// These [ScrollableDetails] can be used to set the [AxisDirection], + /// [ScrollController], [ScrollPhysics] and more for the vertical axis. + final ScrollableDetails verticalDetails; + + /// The configuration of the horizontal Scrollable. + /// + /// These [ScrollableDetails] can be used to set the [AxisDirection], + /// [ScrollController], [ScrollPhysics] and more for the horizontal axis. + final ScrollableDetails horizontalDetails; + + /// The [TreeViewport] has an area before and after the visible area to cache + /// rows that are about to become visible when the user scrolls. + /// + /// [TreeRow]s that fall in this cache area are laid out even though they are + /// not (yet) visible on screen. The [cacheExtent] describes how many pixels + /// the cache area extends before the leading edge and after the trailing edge + /// of the viewport. + /// + /// See also: + /// + /// * [TwoDimensionalScrollView.cacheExtent], the area beyond the viewport + /// bounds where soon-to-be-visible children are rendered. + final double? cacheExtent; + + /// Whether scrolling gestures should lock to one axes, allow free movement + /// in both axes, or be evaluated on a weighted scale. + /// + /// Defaults to [DiagonalDragBehavior.none], locking axes to receive input one + /// at a time. + final DiagonalDragBehavior diagonalDragBehavior; + + /// Determines the way that drag start behavior is handled. + /// + /// By default, the drag start behavior is [DragStartBehavior.start]. + /// + /// See also: + /// + /// * [TwoDimensionalScrollView.dragStartBehavior] + final DragStartBehavior dragStartBehavior; + + /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will + /// dismiss the keyboard automatically. + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// The bounds of the [TreeViewport] will be clipped (or not) according to + /// this option. + /// + /// See the enum [Clip] for details of all possible options and their common + /// use cases. + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + /// Whether to wrap each row of the tree in an [AutomaticKeepAlive]. + /// + /// Typically, lazily laid out children are wrapped in [AutomaticKeepAlive] + /// widgets so that the children can use [KeepAliveNotification]s to preserve + /// their state when they would otherwise be garbage collected off-screen. + /// + /// This feature (and [addRepaintBoundaries]) must be disabled if the children + /// are going to manually maintain their [KeepAlive] state. It may also be + /// more efficient to disable this feature if it is known ahead of time that + /// none of the children will ever try to keep themselves alive. + /// + /// Defaults to true. + final bool addAutomaticKeepAlives; + + /// Whether to wrap each row in a [RepaintBoundary]. + /// + /// Typically, children in a scrolling container are wrapped in repaint + /// boundaries so that they do not need to be repainted as the list scrolls. + /// If the children are easy to repaint (e.g., solid color blocks or a short + /// snippet of text), it might be more efficient to not add a repaint boundary + /// and instead always repaint the children during scrolling. + /// + /// Defaults to true. + final bool addRepaintBoundaries; + + /// The default [AnimationStyle] used for node expand and collapse animations, + /// when one has not been provided in [toggleAnimationStyle]. + static AnimationStyle defaultToggleAnimationStyle = AnimationStyle( + curve: defaultAnimationCurve, + duration: defaultAnimationDuration, + ); + + /// A default of [Curves.linear], which is used in the tree's expanding and + /// collapsing node animation. + static const Curve defaultAnimationCurve = Curves.linear; + + /// A default [Duration] of 150 milliseconds, which is used in the tree's + /// expanding and collapsing node animation. + static const Duration defaultAnimationDuration = Duration(milliseconds: 150); + + /// A wrapper method for triggering the expansion or collapse of a + /// [TreeViewNode]. + /// + /// Use as part of [TreeView.defaultTreeNodeBuilder] to wrap the leading icon + /// of parent [TreeViewNode]s such that tapping on it triggers the animation. + /// + /// If defining your own [TreeView.treeNodeBuilder], this method can be used + /// to wrap any part, or all, of the returned widget in order to trigger the + /// change in state for the node when tapped. + /// + /// The gesture uses [HitTestBehavior.translucent], so as to not conflict + /// with any [TreeRow.recognizerFactories] or other interactive content in the + /// [TreeRow]. + static Widget wrapChildToToggleNode({ + required TreeViewNode node, + required Widget child, + }) { + return Builder(builder: (BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + TreeViewController.of(context).toggleNode(node); + }, + child: child, + ); + }); + } + + /// Returns the fixed height, default [TreeRow] for rows in the tree, + /// which is 40 pixels. + /// + /// Used by [TreeView.treeRowBuilder]. + static TreeRow defaultTreeRowBuilder(TreeViewNode node) { + return const TreeRow( + extent: FixedTreeRowExtent(_kDefaultRowExtent), + ); + } + + /// Default builder for the widget representing a given [TreeViewNode] in the + /// tree. + /// + /// Used by [TreeView.treeNodeBuilder]. + /// + /// This will return a [Row] containing the [toString] of + /// [TreeViewNode.content]. If the [TreeViewNode] is a parent of additional + /// nodes, an arrow icon will precede the content, and will trigger an expand + /// and collapse animation when tapped based on the + /// [TreeView.toggleAnimationStyle]. + static Widget defaultTreeNodeBuilder( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + final Duration animationDuration = + toggleAnimationStyle.duration ?? TreeView.defaultAnimationDuration; + final Curve animationCurve = + toggleAnimationStyle.curve ?? TreeView.defaultAnimationCurve; + final int index = TreeViewController.of(context).getActiveIndexFor(node)!; + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row(children: [ + // Icon for parent nodes + TreeView.wrapChildToToggleNode( + node: node, + child: SizedBox.square( + dimension: 30.0, + child: node.children.isNotEmpty + ? AnimatedRotation( + key: ValueKey(index), + turns: node.isExpanded ? 0.25 : 0.0, + duration: animationDuration, + curve: animationCurve, + // Renders a unicode right-facing arrow. > + child: const Icon(IconData(0x25BA), size: 14), + ) + : null, + ), + ), + // Spacer + const SizedBox(width: 8.0), + // Content + Text(node.content.toString()), + ]), + ); + } + + @override + State> createState() => _TreeViewState(); +} + +// Used in TreeViewState for code simplicity. +typedef _AnimationRecord = ({ + AnimationController controller, + CurvedAnimation animation, + UniqueKey key, +}); + +class _TreeViewState extends State> + with TickerProviderStateMixin, TreeViewStateMixin { + TreeViewController get controller => _treeController!; + TreeViewController? _treeController; + + // The flat representation of the tree, omitting nodes that are not active. + final List> _activeNodes = >[]; + final Map _rowDepths = {}; + bool _shouldUnpackNode(TreeViewNode node) { + if (node.children.isEmpty) { + // No children to unpack. + return false; + } + if (_currentAnimationForParent[node] != null) { + // Whether expanding or collapsing, the child nodes are still active, so + // unpack. + return true; + } + // If we are not animating, respect node.isExpanded; + return node.isExpanded; + } + + // Flattens the tree, omitting nodes that are not active. + void _unpackActiveNodes({ + int depth = 0, + List>? nodes, + TreeViewNode? parent, + }) { + if (nodes == null) { + _activeNodes.clear(); + _rowDepths.clear(); + nodes = widget.tree; + } + for (final TreeViewNode node in nodes) { + node._depth = depth; + node._parent = parent; + _activeNodes.add(node); + _rowDepths[_activeNodes.length - 1] = depth; + if (_shouldUnpackNode(node)) { + _unpackActiveNodes( + depth: depth + 1, + nodes: node.children, + parent: node, + ); + } + } + } + + final Map, _AnimationRecord> _currentAnimationForParent = + , _AnimationRecord>{}; + final Map _activeAnimations = + {}; + + @override + void initState() { + _unpackActiveNodes(); + assert( + widget.controller?._state == null, + 'The provided TreeViewController is already associated with another ' + 'TreeView. A TreeViewController can only be associated with one ' + 'TreeView.', + ); + _treeController = widget.controller ?? TreeViewController(); + _treeController!._state = this; + super.initState(); + } + + @override + void didUpdateWidget(TreeView oldWidget) { + super.didUpdateWidget(oldWidget); + // Internal or provided, there is always a tree controller. + assert(_treeController != null); + if (oldWidget.controller == null && widget.controller != null) { + // A new tree controller has been provided, update and dispose of the + // internally generated one. + _treeController!._state = null; + _treeController = widget.controller; + _treeController!._state = this; + } else if (oldWidget.controller != null && widget.controller == null) { + // A tree controller had been provided, but was removed. We need to create + // one internally. + assert(oldWidget.controller == _treeController); + oldWidget.controller!._state = null; + _treeController = TreeViewController(); + _treeController!._state = this; + } else if (oldWidget.controller != widget.controller) { + assert(oldWidget.controller != null); + assert(widget.controller != null); + assert(oldWidget.controller == _treeController); + // The tree is still being provided a controller, but it has changed. Just + // update it. + _treeController!._state = null; + _treeController = widget.controller; + _treeController!._state = this; + } + // Internal or provided, there is always a tree controller. + assert(_treeController != null); + assert(_treeController!._state != null); + _unpackActiveNodes(); + } + + @override + void dispose() { + _treeController!._state = null; + for (final _AnimationRecord record in _currentAnimationForParent.values) { + record.animation.dispose(); + record.controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _TreeView( + primary: widget.primary, + mainAxis: widget.mainAxis, + horizontalDetails: widget.horizontalDetails, + verticalDetails: widget.verticalDetails, + cacheExtent: widget.cacheExtent, + diagonalDragBehavior: widget.diagonalDragBehavior, + dragStartBehavior: widget.dragStartBehavior, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + clipBehavior: widget.clipBehavior, + rowCount: _activeNodes.length, + activeAnimations: _activeAnimations, + rowDepths: _rowDepths, + nodeBuilder: (BuildContext context, ChildVicinity vicinity) { + vicinity = vicinity as TreeVicinity; + final TreeViewNode node = _activeNodes[vicinity.row]; + assert(vicinity.depth == node.depth); + Widget child = widget.treeNodeBuilder( + context, + node, + widget.toggleAnimationStyle ?? TreeView.defaultToggleAnimationStyle, + ); + + if (widget.addRepaintBoundaries) { + child = RepaintBoundary(child: child); + } + + return child; + }, + rowBuilder: (TreeVicinity vicinity) { + return widget.treeRowBuilder(_activeNodes[vicinity.row]); + }, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + indentation: widget.indentation.value, + ); + } + + // TreeViewStateMixin Implementation + + @override + bool isExpanded(TreeViewNode node) { + return _getNode(node.content, widget.tree)?.isExpanded ?? false; + } + + @override + bool isActive(TreeViewNode node) => _activeNodes.contains(node); + + @override + TreeViewNode? getNodeFor(T content) => _getNode(content, widget.tree); + TreeViewNode? _getNode(T content, List> tree) { + final List> nextDepth = >[]; + for (final TreeViewNode node in tree) { + if (node.content == content) { + return node; + } + if (node.children.isNotEmpty) { + nextDepth.addAll(node.children); + } + } + if (nextDepth.isNotEmpty) { + return _getNode(content, nextDepth); + } + return null; + } + + @override + int? getActiveIndexFor(TreeViewNode node) { + if (_activeNodes.contains(node)) { + return _activeNodes.indexOf(node); + } + return null; + } + + @override + void expandAll() { + final List> activeNodesToExpand = >[]; + _expandAll(widget.tree, activeNodesToExpand); + activeNodesToExpand.reversed.forEach(toggleNode); + } + + void _expandAll( + List> tree, + List> activeNodesToExpand, + ) { + for (final TreeViewNode node in tree) { + if (node.children.isNotEmpty) { + // This is a parent node. + // Expand all the children, and their children. + _expandAll(node.children, activeNodesToExpand); + if (!node.isExpanded) { + // The node itself needs to be expanded. + if (_activeNodes.contains(node)) { + // This is an active node in the tree, add to + // the list to toggle once all hidden nodes + // have been handled. + activeNodesToExpand.add(node); + } else { + // This is a hidden node. Update its expanded state. + node._expanded = true; + } + } + } + } + } + + @override + void collapseAll() { + final List> activeNodesToCollapse = >[]; + _collapseAll(widget.tree, activeNodesToCollapse); + activeNodesToCollapse.reversed.forEach(toggleNode); + } + + void _collapseAll( + List> tree, + List> activeNodesToCollapse, + ) { + for (final TreeViewNode node in tree) { + if (node.children.isNotEmpty) { + // This is a parent node. + // Collapse all the children, and their children. + _collapseAll(node.children, activeNodesToCollapse); + if (node.isExpanded) { + // The node itself needs to be collapsed. + if (_activeNodes.contains(node)) { + // This is an active node in the tree, add to + // the list to toggle once all hidden nodes + // have been handled. + activeNodesToCollapse.add(node); + } else { + // This is a hidden node. Update its expanded state. + node._expanded = false; + } + } + } + } + } + + void _updateActiveAnimations() { + // The indexes of various child node animations can change constantly based + // on more nodes being expanded or collapsed. Compile the indexes and their + // animations keys each time we build with an updated active node list. + _activeAnimations.clear(); + for (final TreeViewNode node in _currentAnimationForParent.keys) { + final _AnimationRecord animationRecord = + _currentAnimationForParent[node]!; + final int leadingChildIndex = _activeNodes.indexOf(node) + 1; + final TreeViewNodesAnimation animatingChildren = ( + fromIndex: leadingChildIndex, + toIndex: leadingChildIndex + node.children.length - 1, + value: animationRecord.animation.value, + ); + _activeAnimations[animationRecord.key] = animatingChildren; + } + } + + @override + void toggleNode(TreeViewNode node) { + assert(_activeNodes.contains(node)); + if (node.children.isEmpty) { + // No state to change. + return; + } + setState(() { + node._expanded = !node._expanded; + if (widget.onNodeToggle != null) { + widget.onNodeToggle!(node); + } + final AnimationController controller = + _currentAnimationForParent[node]?.controller ?? + AnimationController( + value: node._expanded ? 0.0 : 1.0, + vsync: this, + duration: widget.toggleAnimationStyle?.duration ?? + TreeView.defaultAnimationDuration, + ); + controller + ..addStatusListener((AnimationStatus status) { + switch (status) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + _currentAnimationForParent[node]!.controller.dispose(); + _currentAnimationForParent.remove(node); + _updateActiveAnimations(); + case AnimationStatus.forward: + case AnimationStatus.reverse: + } + }) + ..addListener(() { + setState(() { + _updateActiveAnimations(); + }); + }); + + switch (controller.status) { + case AnimationStatus.forward: + case AnimationStatus.reverse: + // We're interrupting an animation already in progress. + controller.stop(); + case AnimationStatus.dismissed: + case AnimationStatus.completed: + } + + final CurvedAnimation newAnimation = CurvedAnimation( + parent: controller, + curve: widget.toggleAnimationStyle?.curve ?? + TreeView.defaultAnimationCurve, + ); + _currentAnimationForParent[node] = ( + controller: controller, + animation: newAnimation, + // This key helps us keep track of the lifetime of this animation in the + // render object, since the indexes can change at any time. + key: UniqueKey(), + ); + switch (node._expanded) { + case true: + // Expanding + _unpackActiveNodes(); + controller.forward(); + case false: + // Collapsing + controller.reverse().then((_) { + _unpackActiveNodes(); + }); + } + }); + } +} + +class _TreeView extends TwoDimensionalScrollView { + _TreeView({ + super.primary, + super.mainAxis, + super.horizontalDetails, + super.verticalDetails, + super.cacheExtent, + super.diagonalDragBehavior = DiagonalDragBehavior.none, + super.dragStartBehavior, + super.keyboardDismissBehavior, + super.clipBehavior, + required TwoDimensionalIndexedWidgetBuilder nodeBuilder, + required TreeVicinityToRowBuilder rowBuilder, + required this.activeAnimations, + required this.rowDepths, + required this.indentation, + required int rowCount, + bool addAutomaticKeepAlives = true, + }) : assert(verticalDetails.direction == AxisDirection.down), + assert(horizontalDetails.direction == AxisDirection.right), + super( + delegate: TreeRowBuilderDelegate( + nodeBuilder: nodeBuilder, + rowBuilder: rowBuilder, + rowCount: rowCount, + addAutomaticKeepAlives: addAutomaticKeepAlives, + )); + + final Map activeAnimations; + final Map rowDepths; + final double indentation; + + @override + TreeViewport buildViewport( + BuildContext context, + ViewportOffset verticalOffset, + ViewportOffset horizontalOffset, + ) { + return TreeViewport( + verticalOffset: verticalOffset, + verticalAxisDirection: verticalDetails.direction, + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalDetails.direction, + delegate: delegate as TreeRowDelegateMixin, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + activeAnimations: activeAnimations, + rowDepths: rowDepths, + indentation: indentation, + ); + } +} + +/// A widget through which a portion of a tree of [TreeViewNode] children are +/// viewed as rows, typically in combination with a [TreeView]. +class TreeViewport extends TwoDimensionalViewport { + /// Creates a viewport for [Widget]s that extend and scroll in both + /// horizontal and vertical dimensions. + const TreeViewport({ + super.key, + required super.verticalOffset, + required super.verticalAxisDirection, + required super.horizontalOffset, + required super.horizontalAxisDirection, + required TreeRowDelegateMixin super.delegate, + super.cacheExtent, + super.clipBehavior, + required this.activeAnimations, + required this.rowDepths, + required this.indentation, + }) : assert(verticalAxisDirection == AxisDirection.down && + horizontalAxisDirection == AxisDirection.right), + // This is fixed as there is currently only one traversal pattern, https://github.com/flutter/flutter/issues/148357 + super(mainAxis: Axis.vertical); + + /// The currently active [TreeViewNode] animations. + /// + /// Since the indexing of animating nodes can change at any time from + /// inserting and removing them from the tree, the unique key is used to track + /// an animation of nodes independent of their indexing across frames. + final Map activeAnimations; + + /// The depth of each active [TreeNode]. + final Map rowDepths; + + /// The number of pixels by which child nodes will be offset in the cross axis + /// based on their [TreeViewNode.depth]. + /// + /// If zero, can alternatively offset children in [TreeView.treeRowBuilder] + /// for more options to customize the indented space. + final double indentation; + + @override + RenderTreeViewport createRenderObject(BuildContext context) { + return RenderTreeViewport( + activeAnimations: activeAnimations, + rowDepths: rowDepths, + indentation: indentation, + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalAxisDirection, + verticalOffset: verticalOffset, + verticalAxisDirection: verticalAxisDirection, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + delegate: delegate as TreeRowDelegateMixin, + childManager: context as TwoDimensionalChildManager, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderTreeViewport renderObject, + ) { + renderObject + ..activeAnimations = activeAnimations + ..rowDepths = rowDepths + ..indentation = indentation + ..horizontalOffset = horizontalOffset + ..horizontalAxisDirection = horizontalAxisDirection + ..verticalOffset = verticalOffset + ..verticalAxisDirection = verticalAxisDirection + ..cacheExtent = cacheExtent + ..clipBehavior = clipBehavior + ..delegate = delegate as TreeRowDelegateMixin; + } +} diff --git a/packages/two_dimensional_scrollables/lib/src/tree_view/tree_core.dart b/packages/two_dimensional_scrollables/lib/src/tree_view/tree_core.dart new file mode 100644 index 00000000000..2c4caea45e3 --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/tree_view/tree_core.dart @@ -0,0 +1,140 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +import 'tree.dart'; + +// The classes in these files follow the same pattern as the one dimensional +// sliver tree in the framework. +// +// After rolling to stable, these classes may be deprecated, or more likely +// made to be typedefs/subclasses of the framework core tree components. They +// could also live on if at a later date the 2D TreeView deviates or adds +// special features not relevant to the 1D sliver components of the framework. + +/// Signature for a function that is called when a [TreeViewNode] is toggled, +/// changing its expanded state. +/// +/// See also: +/// +/// * [TreeViewNode.toggleNode], for controlling node expansion +/// programmatically. +typedef TreeViewNodeCallback = void Function(TreeViewNode node); + +/// A mixin for classes implementing a tree structure as expected by a +/// [TreeViewController]. +/// +/// Used by [TreeView] to implement an interface for the [TreeViewController]. +/// +/// This allows the [TreeViewController] to be used in other widgets that +/// implement this interface. +/// +/// The type [T] correlates to the type of [TreeView] and [TreeViewNode], +/// representing the type of [TreeViewNode.content]. +mixin TreeViewStateMixin { + /// Returns whether or not the given [TreeViewNode] is expanded, regardless of + /// whether or not it is active in the tree. + bool isExpanded(TreeViewNode node); + + /// Returns whether or not the given [TreeViewNode] is enclosed within its + /// parent [TreeViewNode]. + /// + /// If the [TreeViewNode.parent] [isExpanded] (and all its parents are + /// expanded), or this is a root node, the given node is active and this + /// method will return true. This does not reflect whether or not the node is + /// visible in the [Viewport]. + bool isActive(TreeViewNode node); + + /// Switches the given [TreeViewNode]s expanded state. + /// + /// May trigger an animation to reveal or hide the node's children based on + /// the [TreeView.toggleAnimationStyle]. + /// + /// If the node does not have any children, nothing will happen. + void toggleNode(TreeViewNode node); + + /// Closes all parent [TreeViewNode]s in the tree. + void collapseAll(); + + /// Expands all parent [TreeViewNode]s in the tree. + void expandAll(); + + /// Retrieves the [TreeViewNode] containing the associated content, if it + /// exists. + /// + /// If no node exists, this will return null. This does not reflect whether + /// or not a node [isActive], or if it is visible in the viewport. + TreeViewNode? getNodeFor(T content); + + /// Returns the current row index of the given [TreeViewNode]. + /// + /// If the node is not currently active in the tree, meaning its parent is + /// collapsed, this will return null. + int? getActiveIndexFor(TreeViewNode node); +} + +/// Represents the animation of the children of a parent [TreeViewNode] that +/// are animating into or out of view. +/// +/// The [fromIndex] and [toIndex] are identify the animating children following +/// the parent, with the [value] representing the status of the current +/// animation. The value of [toIndex] is inclusive, meaning the child at that +/// index is included in the animating segment. +/// +/// Provided to [RenderTreeViewport] as part of +/// [RenderTreeViewport.activeAnimations] by [TreeView] to properly offset +/// animating children. +typedef TreeViewNodesAnimation = ({ + int fromIndex, + int toIndex, + double value, +}); + +/// The style of indentation for [TreeViewNode]s in a [TreeView], as handled +/// by [RenderTreeViewport]. +/// +/// By default, the indentation is handled by [RenderTreeViewport]. Child nodes +/// are offset by the indentation specified by +/// [TreeViewIndentationType.value] in the cross axis of the viewport. This +/// means the space allotted to the indentation will not be part of the space +/// made available to the Widget returned by [TreeView.treeNodeBuilder]. +/// +/// Alternatively, the indentation can be implemented in +/// [TreeView.treeNodeBuilder], with the depth of the given tree row accessed +/// by [TreeViewNode.depth]. This allows for more customization in building +/// tree rows, such as filling the indented area with decorations or ink +/// effects. +class TreeViewIndentationType { + const TreeViewIndentationType._internal(double value) : _value = value; + + /// The number of pixels by which [TreeViewNode]s will be offset according + /// to their [TreeViewNode.depth]. + double get value => _value; + final double _value; + + /// The default indentation of child [TreeViewNode]s in a [TreeView]. + /// + /// Child nodes will be offset by 10 pixels for each level in the tree. + static const TreeViewIndentationType standard = + TreeViewIndentationType._internal(10.0); + + /// Configures no offsetting of child nodes in a [TreeView]. + /// + /// Useful if the indentation is implemented in the + /// [TreeView.treeNodeBuilder] instead for more customization options. + /// + /// Child nodes will not be offset in the tree. + static const TreeViewIndentationType none = + TreeViewIndentationType._internal(0.0); + + /// Configures a custom offset for indenting child nodes in a [TreeView]. + /// + /// Child nodes will be offset by the provided number of pixels in the tree. + /// The [value] must be a non negative number. + static TreeViewIndentationType custom(double value) { + assert(value >= 0.0); + return TreeViewIndentationType._internal(value); + } +} diff --git a/packages/two_dimensional_scrollables/lib/src/tree_view/tree_delegate.dart b/packages/two_dimensional_scrollables/lib/src/tree_view/tree_delegate.dart new file mode 100644 index 00000000000..c96bc789713 --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/tree_view/tree_delegate.dart @@ -0,0 +1,125 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +import 'tree.dart'; +import 'tree_span.dart'; + +/// Signature for a function that creates a [TreeRow] for a given +/// [TreeViewNode] in a [TreeView]. +/// +/// Used by the [TreeViewDelegateMixin.buildRow] to configure rows in the +/// [TreeView]. +typedef TreeViewRowBuilder = TreeRow Function(TreeViewNode node); + +/// Signature for a function that creates a [Widget] to represent the given +/// [TreeViewNode] in the [TreeView]. +/// +/// Used by [TreeView.treeRowBuilder] to build rows on demand for the +/// tree. +typedef TreeViewNodeBuilder = Widget Function( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, +); + +/// The position of a [TreeRow] in a [TreeViewport] in relation +/// to other children of the viewport. +/// +/// This subclass translates the abstract [ChildVicinity.xIndex] and +/// [ChildVicinity.yIndex] into terms of row index and depth for ease of use +/// within the context of a [TreeView]. +@immutable +class TreeVicinity extends ChildVicinity { + /// Creates a reference to a [TreeRow] in a [TreeView], with the [xIndex] and + /// [yIndex] converted to terms of [depth] and [row], respectively. + const TreeVicinity({ + required int depth, + required int row, + }) : super(xIndex: depth, yIndex: row); + + /// The row index of the [TreeRow] in the [TreeView]. + /// + /// Equivalent to the [yIndex]. + int get row => yIndex; + + /// The depth of the [TreeRow] in the [TreeView]. + /// + /// Root [TreeViewNode]s have a depth of 0. + /// + /// Equivalent to the [xIndex]. + int get depth => xIndex; + + @override + String toString() => '(row: $row, depth: $depth)'; +} + +/// A mixin that defines the model for a [TwoDimensionalChildDelegate] to be +/// used with a [TreeView]. +mixin TreeRowDelegateMixin on TwoDimensionalChildDelegate { + /// The number of rows that the tree has active nodes for. + /// + /// The [buildRow] method will be called for [TreeViewNode]s that are + /// currently active, meaning they are not contained within an unexpanded + /// parent node. + /// + /// The [buildRow] method must provide a valid [TreeRow] for all active nodes. + /// + /// If the value returned by this getter changes throughout the lifetime of + /// the delegate object, [notifyListeners] must be called. + int get rowCount; + + /// Builds the [TreeRow] that describe the row for the provided + /// [TreeVicinity]. + /// + /// The builder must return a valid [TreeRow] for all active nodes in the + /// tree. + TreeRow buildRow(TreeVicinity vicinity); +} + +/// Returns a [TreeRow] for the given [TreeVicinity] in the [TreeView]. +typedef TreeVicinityToRowBuilder = TreeRow Function(TreeVicinity); + +/// A delegate that supplies nodes for a [TreeViewport] on demand using a +/// builder callback. +/// +/// This is not typically used directly, instead being created and managed by +/// the [TreeView] so that the builder can be called for only those +/// [TreeViewNode]s that are currently active in the [TreeView]. +/// +/// The [rowCount] is determined by the number of active nodes in the +/// [TreeView]. +class TreeRowBuilderDelegate extends TwoDimensionalChildBuilderDelegate + with TreeRowDelegateMixin { + /// Creates a lazy building delegate to use with a [TreeView]. + TreeRowBuilderDelegate({ + required int rowCount, + super.addAutomaticKeepAlives, + required TwoDimensionalIndexedWidgetBuilder nodeBuilder, + required TreeVicinityToRowBuilder rowBuilder, + }) : assert(rowCount >= 0), + _rowBuilder = rowBuilder, + super( + builder: nodeBuilder, + // No maxXIndex, since we do not know the max depth. + maxYIndex: rowCount - 1, + // repaintBoundaries handled by TreeView + addRepaintBoundaries: false, + ); + + @override + int get rowCount => maxYIndex! + 1; + + set rowCount(int value) { + assert(value >= 0); + maxYIndex = value - 1; + } + + /// Builds the [TreeRow] that describes the row for the provided + /// [TreeVicinity]. + final TreeVicinityToRowBuilder _rowBuilder; + @override + TreeRow buildRow(TreeVicinity vicinity) => _rowBuilder(vicinity); +} diff --git a/packages/two_dimensional_scrollables/lib/src/tree_view/tree_span.dart b/packages/two_dimensional_scrollables/lib/src/tree_view/tree_span.dart new file mode 100644 index 00000000000..b909279482e --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/tree_view/tree_span.dart @@ -0,0 +1,127 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/painting.dart'; + +import '../common/span.dart'; + +/// Defines the leading and trailing padding values of a [TreeRow]. +typedef TreeRowPadding = SpanPadding; + +/// Defines the extent, visual appearance, and gesture handling of a row in a +/// [TreeView]. +typedef TreeRow = Span; + +/// Delegate passed to [TreeSpanExtent.calculateExtent] from the +/// [RenderTreeViewport] during layout. +/// +/// Provides access to metrics from the [TreeView] that a [TreeRowExtent] may +/// need to calculate its extent. +/// +/// Extents will not be computed for every frame unless the delegate has been +/// updated. Otherwise, after the extents are computed during the first layout +/// pass, they are cached and reused in subsequent frames. +typedef TreeRowExtentDelegate = SpanExtentDelegate; + +/// Defines the extent, or height, of a [TreeRow]. +typedef TreeRowExtent = SpanExtent; + +/// A [TreeRow] with a fixed [pixels] height. +typedef FixedTreeRowExtent = FixedSpanExtent; + +/// Specified the [TreeRow] height as a fraction of the viewport extent. +/// +/// For example, a row with a 1.0 as [fraction] will be as tall as the +/// viewport. +typedef FractionalTreeRowExtent = FractionalSpanExtent; + +/// Specifies that the row should occupy the remaining space in the viewport. +/// +/// If the previous [TreeRow]s can already fill out the viewport, this will +/// evaluate the row's height to zero. If the previous rows cannot fill out the +/// viewport, this row's extent will be whatever space is left to fill out the +/// viewport. +/// +/// To avoid that the row's extent evaluates to zero, consider combining this +/// extent with another extent. The following example will make sure that the +/// span's extent is at least 200 pixels, but if there's more than that available +/// in the viewport, it will fill all that space: +/// +/// ```dart +/// const MaxTreeRowExtent(FixedTreeRowExtent(200.0), RemainingTreeRowExtent()); +/// ``` +typedef RemainingTreeRowExtent = RemainingSpanExtent; + +/// Signature for a function that combines the result of two +/// [TreeRowExtent.calculateExtent] invocations. +/// +/// Used by [CombiningTreeRowExtent]; +typedef TreeRowExtentCombiner = SpanExtentCombiner; + +/// Runs the result of two [TreeRowExtent]s through a `combiner` function +/// to determine the ultimate pixel height of a tree row. +typedef CombiningTreeRowExtent = CombiningSpanExtent; + +/// Returns the larger pixel extent of the two provided [TreeRowExtent]. +typedef MaxTreeRowExtent = MaxSpanExtent; + +/// Returns the smaller pixel extent of the two provided [TreeRowExtent]. +typedef MinTreeRowExtent = MinSpanExtent; + +/// A decoration for a [TreeRow]. +typedef TreeRowDecoration = SpanDecoration; + +/// Describes the border for a [TreeRow]. +class TreeRowBorder extends SpanBorder { + /// Creates a [TreeRowBorder]. + const TreeRowBorder({ + BorderSide top = BorderSide.none, + BorderSide bottom = BorderSide.none, + this.left = BorderSide.none, + this.right = BorderSide.none, + }) : super(leading: top, trailing: bottom); + + /// Creates a [TreeRowBorder] with the provided [BorderSide] applied to all + /// sides. + const TreeRowBorder.all(BorderSide side) + : left = side, + right = side, + super(leading: side, trailing: side); + + /// The border to paint on the top, or leading edge of the [TreeRow]. + BorderSide get top => leading; + + /// The border to paint on the top, or leading edge of the [TreeRow]. + BorderSide get bottom => trailing; + + /// The border to draw on the left side of the [TreeRow]. + final BorderSide left; + + /// The border to draw on the right side of the [TreeRow]. + final BorderSide right; + + @override + void paint( + SpanDecorationPaintDetails details, + BorderRadius? borderRadius, + ) { + final Border border = Border( + top: top, + bottom: bottom, + left: left, + right: right, + ); + border.paint( + details.canvas, + details.rect, + borderRadius: borderRadius, + ); + } +} + +/// Provides the details of a given [TreeRowDecoration] for painting. +/// +/// Created during paint by the [RenderTreeViewport] for the +/// [TreeRow.foregroundDecoration] and [TreeRow.backgroundDecoration]. +typedef TreeRowDecorationPaintDetails = SpanDecorationPaintDetails; diff --git a/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart b/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart index f19cdb4e343..9e8ecc38487 100644 --- a/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart +++ b/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// The [TableView] and associated widgets. +/// The [TableView], [TreeView], and associated widgets. /// /// To use, import `package:two_dimensional_scrollables/two_dimensional_scrollables.dart`. library two_dimensional_scrollables; @@ -13,3 +13,9 @@ export 'src/table_view/table.dart'; export 'src/table_view/table_cell.dart'; export 'src/table_view/table_delegate.dart'; export 'src/table_view/table_span.dart'; + +export 'src/tree_view/render_tree.dart'; +export 'src/tree_view/tree.dart'; +export 'src/tree_view/tree_core.dart'; +export 'src/tree_view/tree_delegate.dart'; +export 'src/tree_view/tree_span.dart'; diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml index 1376d5fc29b..45845c7afe0 100644 --- a/packages/two_dimensional_scrollables/pubspec.yaml +++ b/packages/two_dimensional_scrollables/pubspec.yaml @@ -1,12 +1,12 @@ name: two_dimensional_scrollables description: Widgets that scroll using the two dimensional scrolling foundation. -version: 0.2.1 +version: 0.3.1 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+ environment: - sdk: '>=3.2.0 <4.0.0' - flutter: ">=3.16.0" + sdk: '>=3.3.0 <4.0.0' + flutter: ">=3.19.0" dependencies: flutter: 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 ce879b75b27..94955c14936 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -4135,7 +4135,7 @@ void main() { class _NullBuildContext implements BuildContext, TwoDimensionalChildManager { @override - dynamic noSuchMethod(Invocation invocation) => throw UnimplementedError(); + Object? noSuchMethod(Invocation invocation) => throw UnimplementedError(); } RenderTableViewport getViewport(WidgetTester tester, Key childKey) { diff --git a/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart b/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart new file mode 100644 index 00000000000..3b239380d53 --- /dev/null +++ b/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart @@ -0,0 +1,971 @@ +// 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/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +const TreeRow row = TreeRow(extent: FixedTreeRowExtent(100)); + +TreeRow getTappableRow(TreeViewNode node, VoidCallback callback) { + return TreeRow( + extent: const FixedTreeRowExtent(100), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => t.onTap = () => callback(), + ), + }, + ); +} + +TreeRow getMouseTrackingRow({ + PointerEnterEventListener? onEnter, + PointerExitEventListener? onExit, +}) { + return TreeRow( + extent: const FixedTreeRowExtent(100), + onEnter: onEnter, + onExit: onExit, + cursor: SystemMouseCursors.cell, + ); +} + +List> _setUpNodes() { + return >[ + TreeViewNode('First'), + TreeViewNode( + 'Second', + children: >[ + TreeViewNode( + 'alpha', + children: >[ + TreeViewNode('uno'), + TreeViewNode('dos'), + TreeViewNode('tres'), + ], + ), + TreeViewNode('beta'), + TreeViewNode('kappa'), + ], + ), + TreeViewNode( + 'Third', + expanded: true, + children: >[ + TreeViewNode('gamma'), + TreeViewNode('delta'), + TreeViewNode('epsilon'), + ], + ), + TreeViewNode('Fourth'), + ]; +} + +List> treeNodes = _setUpNodes(); + +void main() { + group('RenderTreeViewport', () { + setUp(() { + treeNodes = _setUpNodes(); + }); + + test('asserts proper axis directions', () { + RenderTreeViewport? treeViewport; + expect( + () { + treeViewport = RenderTreeViewport( + verticalOffset: TestOffset(), + verticalAxisDirection: AxisDirection.up, + horizontalOffset: TestOffset(), + horizontalAxisDirection: AxisDirection.right, + delegate: TreeRowBuilderDelegate( + rowCount: 0, + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => const TreeRow( + extent: FixedTreeRowExtent(40.0), + ), + ), + activeAnimations: const {}, + rowDepths: const {}, + indentation: 0.0, + childManager: _NullBuildContext(), + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('verticalAxisDirection == AxisDirection.down'), + ), + ), + ); + expect( + () { + treeViewport = RenderTreeViewport( + verticalOffset: TestOffset(), + verticalAxisDirection: AxisDirection.down, + horizontalOffset: TestOffset(), + horizontalAxisDirection: AxisDirection.left, + delegate: TreeRowBuilderDelegate( + rowCount: 0, + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => const TreeRow( + extent: FixedTreeRowExtent(40.0), + ), + ), + activeAnimations: const {}, + rowDepths: const {}, + indentation: 0.0, + childManager: _NullBuildContext(), + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('horizontalAxisDirection == AxisDirection.right'), + ), + ), + ); + expect(treeViewport, isNull); + }); + + testWidgets('TreeRow gesture hit testing', (WidgetTester tester) async { + int tapCounter = 0; + final List log = []; + final TreeView treeView = TreeView( + tree: treeNodes, + treeRowBuilder: (TreeViewNode node) { + if (node.depth! == 0) { + return getTappableRow( + node, + () { + log.add(node.content); + tapCounter++; + }, + ); + } + return row; + }, + ); + + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pumpAndSettle(); + + // Root level rows are set up for taps. + expect(tapCounter, 0); + await tester.tap(find.text('First')); + await tester.tap(find.text('Second')); + await tester.tap(find.text('Third')); + // Should not be logged. + await tester.tap(find.text('gamma')); + expect(tapCounter, 3); + expect(log, ['First', 'Second', 'Third']); + }); + + testWidgets('mouse handling', (WidgetTester tester) async { + int enterCounter = 0; + int exitCounter = 0; + final TreeView treeView = TreeView( + tree: treeNodes, + treeRowBuilder: (TreeViewNode node) { + if (node.depth! == 0) { + return getMouseTrackingRow( + onEnter: (_) => enterCounter++, + onExit: (_) => exitCounter++, + ); + } + return row; + }, + ); + + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pumpAndSettle(); + // Root row will respond to mouse, child will not + final Offset rootRow = tester.getCenter(find.text('Second')); + final Offset childRow = tester.getCenter(find.text('gamma')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(location: childRow); + expect(enterCounter, 0); + expect(exitCounter, 0); + expect( + RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.basic, + ); + await gesture.moveTo(rootRow); + await tester.pumpAndSettle(); + expect(enterCounter, 1); + expect(exitCounter, 0); + expect( + RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.cell, + ); + await gesture.moveTo(childRow); + await tester.pumpAndSettle(); + expect(enterCounter, 1); + expect(exitCounter, 1); + expect( + RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.basic, + ); + }); + + testWidgets('Scrolls when there is enough content', + (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TreeViewController treeController = TreeViewController(); + addTearDown(verticalController.dispose); + addTearDown(horizontalController.dispose); + final TreeView treeView = TreeView( + controller: treeController, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + tree: treeNodes, + // Exaggerated to exceed viewport bounds. + indentation: TreeViewIndentationType.custom(500), + treeRowBuilder: (_) => row, + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + // Room to scroll + expect(verticalController.position.maxScrollExtent, 100.0); + expect(horizontalController.position.pixels, 0.0); + // Room to scroll + expect(horizontalController.position.maxScrollExtent, 90.0); + + verticalController.jumpTo(10.0); + horizontalController.jumpTo(10.0); + await tester.pump(); + expect(verticalController.position.pixels, 10.0); + expect(verticalController.position.maxScrollExtent, 100.0); + expect(horizontalController.position.pixels, 10.0); + expect(horizontalController.position.maxScrollExtent, 90.0); + + // Collapse a node. The horizontal extent should change to zero, + // and the position should corrrect. + treeController.toggleNode(treeController.getNodeFor('Third')!); + await tester.pumpAndSettle(); + expect(horizontalController.position.pixels, 0.0); + expect(horizontalController.position.maxScrollExtent, 0.0); + }); + + group('Layout', () { + setUp(() { + treeNodes = _setUpNodes(); + }); + + testWidgets('Basic', (WidgetTester tester) async { + // Default layout, custom indentation values, row extents. + TreeView treeView = TreeView( + tree: treeNodes, + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(find.text('First'), findsOneWidget); + expect( + tester.getRect(find.text('First')), + const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0), + ); + expect(find.text('Second'), findsOneWidget); + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + expect(find.text('Third'), findsOneWidget); + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0), + ); + expect(find.text('gamma'), findsOneWidget); + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0), + ); + expect(find.text('delta'), findsOneWidget); + expect( + tester.getRect(find.text('delta')), + const Rect.fromLTRB(56.0, 168.0, 296.0, 192.0), + ); + expect(find.text('epsilon'), findsOneWidget); + expect( + tester.getRect(find.text('epsilon')), + const Rect.fromLTRB(56.0, 208.0, 392.0, 232.0), + ); + expect(find.text('Fourth'), findsOneWidget); + expect( + tester.getRect(find.text('Fourth')), + const Rect.fromLTRB(46.0, 248.0, 334.0, 272.0), + ); + + treeView = TreeView( + tree: treeNodes, + indentation: TreeViewIndentationType.none, + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(find.text('First'), findsOneWidget); + expect( + tester.getRect(find.text('First')), + const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0), + ); + expect(find.text('Second'), findsOneWidget); + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + expect(find.text('Third'), findsOneWidget); + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0), + ); + expect(find.text('gamma'), findsOneWidget); + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(46.0, 128.0, 286.0, 152.0), + ); + expect(find.text('delta'), findsOneWidget); + expect( + tester.getRect(find.text('delta')), + const Rect.fromLTRB(46.0, 168.0, 286.0, 192.0), + ); + expect(find.text('epsilon'), findsOneWidget); + expect( + tester.getRect(find.text('epsilon')), + const Rect.fromLTRB(46.0, 208.0, 382.0, 232.0), + ); + expect(find.text('Fourth'), findsOneWidget); + expect( + tester.getRect(find.text('Fourth')), + const Rect.fromLTRB(46.0, 248.0, 334.0, 272.0), + ); + + treeView = TreeView( + tree: treeNodes, + indentation: TreeViewIndentationType.custom(50.0), + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(find.text('First'), findsOneWidget); + expect( + tester.getRect(find.text('First')), + const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0), + ); + expect(find.text('Second'), findsOneWidget); + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + expect(find.text('Third'), findsOneWidget); + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0), + ); + expect(find.text('gamma'), findsOneWidget); + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(96.0, 128.0, 336.0, 152.0), + ); + expect(find.text('delta'), findsOneWidget); + expect( + tester.getRect(find.text('delta')), + const Rect.fromLTRB(96.0, 168.0, 336.0, 192.0), + ); + expect(find.text('epsilon'), findsOneWidget); + expect( + tester.getRect(find.text('epsilon')), + const Rect.fromLTRB(96.0, 208.0, 432.0, 232.0), + ); + expect(find.text('Fourth'), findsOneWidget); + expect( + tester.getRect(find.text('Fourth')), + const Rect.fromLTRB(46.0, 248.0, 334.0, 272.0), + ); + + treeView = TreeView( + tree: treeNodes, + treeRowBuilder: (TreeViewNode node) { + if (node.depth! == 1) { + // extent == 100 + return row; + } + return TreeView.defaultTreeRowBuilder(node); + }, + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(find.text('First'), findsOneWidget); + expect( + tester.getRect(find.text('First')), + const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0), + ); + expect(find.text('Second'), findsOneWidget); + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + expect(find.text('Third'), findsOneWidget); + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0), + ); + expect(find.text('gamma'), findsOneWidget); + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(56.0, 146.0, 296.0, 194.0), + ); + expect(find.text('delta'), findsOneWidget); + expect( + tester.getRect(find.text('delta')), + const Rect.fromLTRB(56.0, 246.0, 296.0, 294.0), + ); + expect(find.text('epsilon'), findsOneWidget); + expect( + tester.getRect(find.text('epsilon')), + const Rect.fromLTRB(56.0, 346.0, 392.0, 394.0), + ); + expect(find.text('Fourth'), findsOneWidget); + expect( + tester.getRect(find.text('Fourth')), + const Rect.fromLTRB(46.0, 428.0, 334.0, 452.0), + ); + }); + + testWidgets('Animating node segment', (WidgetTester tester) async { + TreeView treeView = TreeView(tree: treeNodes); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(find.text('alpha'), findsNothing); + await tester.tap(find.byType(Icon).first); + await tester.pump(); + // It has now been inserted into the tree, along with the other children + // of the node. + expect(find.text('alpha'), findsOneWidget); + expect( + tester.getRect(find.text('alpha')), + const Rect.fromLTRB(56.0, -32.0, 296.0, -8.0), + ); + expect(find.text('beta'), findsOneWidget); + expect( + tester.getRect(find.text('beta')), + const Rect.fromLTRB(56.0, 8.0, 248.0, 32.0), + ); + expect(find.text('kappa'), findsOneWidget); + expect( + tester.getRect(find.text('kappa')), + const Rect.fromLTRB(56.0, 48.0, 296.0, 72.0), + ); + // Progress the animation. + await tester.pump(const Duration(milliseconds: 50)); + expect( + tester.getRect(find.text('alpha')).top.floor(), + 8.0, + ); + expect(find.text('beta'), findsOneWidget); + expect( + tester.getRect(find.text('beta')).top.floor(), + 48.0, + ); + expect(find.text('kappa'), findsOneWidget); + expect( + tester.getRect(find.text('kappa')).top.floor(), + 88.0, + ); + // Complete the animation + await tester.pumpAndSettle(); + expect(find.text('alpha'), findsOneWidget); + expect( + tester.getRect(find.text('alpha')), + const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0), + ); + expect(find.text('beta'), findsOneWidget); + expect( + tester.getRect(find.text('beta')), + const Rect.fromLTRB(56.0, 128.0, 248.0, 152.0), + ); + expect(find.text('kappa'), findsOneWidget); + expect( + tester.getRect(find.text('kappa')), + const Rect.fromLTRB(56.0, 168.0, 296.0, 192.0), + ); + + // Customize the animation + treeView = TreeView( + tree: treeNodes, + toggleAnimationStyle: AnimationStyle( + duration: const Duration(milliseconds: 500), + curve: Curves.bounceIn, + ), + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + // Still visible from earlier. + expect(find.text('alpha'), findsOneWidget); + expect( + tester.getRect(find.text('alpha')), + const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0), + ); + // Collapse the node now + await tester.tap(find.byType(Icon).first); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + expect(find.text('alpha'), findsOneWidget); + expect( + tester.getRect(find.text('alpha')).top.floor(), + -22, + ); + expect(find.text('beta'), findsOneWidget); + expect( + tester.getRect(find.text('beta')).top.floor(), + 18, + ); + expect(find.text('kappa'), findsOneWidget); + expect( + tester.getRect(find.text('kappa')).top.floor(), + 58, + ); + // Progress the animation. + await tester.pump(const Duration(milliseconds: 200)); + expect(find.text('alpha'), findsOneWidget); + expect( + tester.getRect(find.text('alpha')).top.floor(), + -25, + ); + expect(find.text('beta'), findsOneWidget); + expect( + tester.getRect(find.text('beta')).top.floor(), + 15, + ); + expect(find.text('kappa'), findsOneWidget); + expect( + tester.getRect(find.text('kappa')).top.floor(), + 55.0, + ); + // Complete the animation + await tester.pumpAndSettle(); + expect(find.text('alpha'), findsNothing); + + // Disable the animation + treeView = TreeView( + tree: treeNodes, + toggleAnimationStyle: AnimationStyle.noAnimation, + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + // Not in the tree. + expect(find.text('alpha'), findsNothing); + // Collapse the node now + await tester.tap(find.byType(Icon).first); + await tester.pump(); + // No animating + expect(find.text('alpha'), findsOneWidget); + expect( + tester.getRect(find.text('alpha')), + const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0), + ); + expect(find.text('beta'), findsOneWidget); + expect( + tester.getRect(find.text('beta')), + const Rect.fromLTRB(56.0, 128.0, 248.0, 152.0), + ); + expect(find.text('kappa'), findsOneWidget); + expect( + tester.getRect(find.text('kappa')), + const Rect.fromLTRB(56.0, 168.0, 296.0, 192.0), + ); + }); + + testWidgets('Multiple animating node segments', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: treeNodes, + controller: controller, + ), + )); + await tester.pump(); + expect(find.text('Second'), findsOneWidget); + expect(find.text('alpha'), findsNothing); // Second is collapsed + expect(find.text('Third'), findsOneWidget); + expect(find.text('gamma'), findsOneWidget); // Third is expanded + + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0), + ); + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0), + ); + + // Trigger two animations to run together. + // Collapse Third + await tester.tap(find.byType(Icon).last); + // Expand Second + await tester.tap(find.byType(Icon).first); + await tester.pump(const Duration(milliseconds: 15)); + // Third is collapsing + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0), + ); + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0), + ); + // Second is expanding + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + // alpha has been added and is animating into view. + expect( + tester.getRect(find.text('alpha')).top.floor(), + -32.0, + ); + await tester.pump(const Duration(milliseconds: 15)); + // Third is still collapsing. Third is sliding down + // as Seconds's children slide in, gamma is still exiting. + expect( + tester.getRect(find.text('Third')).top.floor(), + 100.0, + ); + // gamma appears to not have moved, this is because it is + // intersecting both animations, the positive offset of + // Second animation == the negative offset of Third + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0), + ); + // Second is still expanding + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + // alpha is still animating into view. + expect( + tester.getRect(find.text('alpha')).top.floor(), + -20.0, + ); + // Progress the animation further + await tester.pump(const Duration(milliseconds: 15)); + // Third is still collapsing. Third is sliding down + // as Seconds's children slide in, gamma is still exiting. + expect( + tester.getRect(find.text('Third')).top.floor(), + 112.0, + ); + // gamma appears to not have moved, this is because it is + // intersecting both animations, the positive offset of + // Second animation == the negative offset of Third + expect( + tester.getRect(find.text('gamma')), + const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0), + ); + // Second is still expanding + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + // alpha is still animating into view. + expect( + tester.getRect(find.text('alpha')).top.floor(), + -8.0, + ); + // Complete the animations + await tester.pumpAndSettle(); + expect( + tester.getRect(find.text('Third')), + const Rect.fromLTRB(46.0, 208.0, 286.0, 232.0), + ); + // gamma has left the building + expect(find.text('gamma'), findsNothing); + expect( + tester.getRect(find.text('Second')), + const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0), + ); + // alpha is in place. + expect( + tester.getRect(find.text('alpha')), + const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0), + ); + }); + }); + + group('Painting', () { + setUp(() { + treeNodes = _setUpNodes(); + }); + + testWidgets('only paints visible rows', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + addTearDown(verticalController.dispose); + final TreeView treeView = TreeView( + treeRowBuilder: (_) => const TreeRow(extent: FixedTreeRowExtent(400)), + tree: treeNodes, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 600.0); + + bool rowNeedsPaint(String row) { + return find.text(row).evaluate().first.renderObject!.debugNeedsPaint; + } + + expect(rowNeedsPaint('First'), isFalse); + expect(rowNeedsPaint('Second'), isFalse); + expect(rowNeedsPaint('Third'), isTrue); // In cacheExtent + expect(find.text('gamma'), findsNothing); // outside of cacheExtent + }); + + testWidgets('paints decorations correctly', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + addTearDown(verticalController.dispose); + addTearDown(horizontalController.dispose); + const TreeRowDecoration rootForegroundDecoration = TreeRowDecoration( + color: Colors.red, + ); + const TreeRowDecoration rootBackgroundDecoration = TreeRowDecoration( + color: Colors.blue, + ); + const TreeRowDecoration foregroundDecoration = TreeRowDecoration( + color: Colors.orange, + ); + const TreeRowDecoration backgroundDecoration = TreeRowDecoration( + color: Colors.green, + ); + final TreeView treeView = TreeView( + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + tree: treeNodes, + treeRowBuilder: (TreeViewNode node) { + return row.copyWith( + backgroundDecoration: node.depth! == 0 + ? rootBackgroundDecoration + : backgroundDecoration, + foregroundDecoration: node.depth! == 0 + ? rootForegroundDecoration + : foregroundDecoration, + ); + }, + ); + await tester.pumpWidget(MaterialApp(home: treeView)); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(verticalController.position.maxScrollExtent, 100.0); + + expect( + find.byType(TreeViewport), + paints + ..rect( + rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 100.0), + color: const Color(0xff2196f3), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0), + color: const Color(0xff2196f3), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 200.0, 800.0, 300.0), + color: const Color(0xff2196f3), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 300.0, 800.0, 400.0), + color: const Color(0xff4caf50), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 400.0, 800.0, 500.0), + color: const Color(0xff4caf50), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 500.0, 800.0, 600.0), + color: const Color(0xff4caf50), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 600.0, 800.0, 700.0), + color: const Color(0xff2196f3), + ) + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..rect( + rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 100.0), + color: const Color(0xFFF44336), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0), + color: const Color(0xFFF44336), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 200.0, 800.0, 300.0), + color: const Color(0xFFF44336), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 300.0, 800.0, 400.0), + color: const Color(0xFFFF9800), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 400.0, 800.0, 500.0), + color: const Color(0xFFFF9800), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 500.0, 800.0, 600.0), + color: const Color(0xFFFF9800), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 600.0, 800.0, 700.0), + color: const Color(0xFFF44336), + ), + ); + // Change the scroll offset + verticalController.jumpTo(10.0); + await tester.pump(); + expect( + find.byType(TreeViewport), + paints + ..rect( + rect: const Rect.fromLTRB(0.0, -10.0, 800.0, 90.0), + color: const Color(0xff2196f3), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 90.0, 800.0, 190.0), + color: const Color(0xff2196f3), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 190.0, 800.0, 290.0), + color: const Color(0xff2196f3), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 290.0, 800.0, 390.0), + color: const Color(0xff4caf50), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 390.0, 800.0, 490.0), + color: const Color(0xff4caf50), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 490.0, 800.0, 590.0), + color: const Color(0xff4caf50), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 590.0, 800.0, 690.0), + color: const Color(0xff2196f3), + ) + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..paragraph() + ..rect( + rect: const Rect.fromLTRB(0.0, -10.0, 800.0, 90.0), + color: const Color(0xFFF44336), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 90.0, 800.0, 190.0), + color: const Color(0xFFF44336), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 190.0, 800.0, 290.0), + color: const Color(0xFFF44336), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 290.0, 800.0, 390.0), + color: const Color(0xFFFF9800), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 390.0, 800.0, 490.0), + color: const Color(0xFFFF9800), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 490.0, 800.0, 590.0), + color: const Color(0xFFFF9800), + ) + ..rect( + rect: const Rect.fromLTRB(0.0, 590.0, 800.0, 690.0), + color: const Color(0xFFF44336), + ), + ); + }); + }); + }); +} + +class TestOffset extends ViewportOffset { + TestOffset(); + + @override + bool get allowImplicitScrolling => throw UnimplementedError(); + + @override + Future animateTo( + double to, { + required Duration duration, + required Curve curve, + }) { + throw UnimplementedError(); + } + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + throw UnimplementedError(); + } + + @override + bool applyViewportDimension(double viewportDimension) { + throw UnimplementedError(); + } + + @override + void correctBy(double correction) {} + + @override + bool get hasPixels => throw UnimplementedError(); + + @override + void jumpTo(double pixels) {} + + @override + double get pixels => throw UnimplementedError(); + + @override + ScrollDirection get userScrollDirection => throw UnimplementedError(); +} + +class _NullBuildContext implements BuildContext, TwoDimensionalChildManager { + @override + Object? noSuchMethod(Invocation invocation) => throw UnimplementedError(); +} diff --git a/packages/two_dimensional_scrollables/test/tree_view/tree_core_test.dart b/packages/two_dimensional_scrollables/test/tree_view/tree_core_test.dart new file mode 100644 index 00000000000..d1c4d425f8d --- /dev/null +++ b/packages/two_dimensional_scrollables/test/tree_view/tree_core_test.dart @@ -0,0 +1,21 @@ +// 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:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +void main() { + group('TreeViewIndentationType', () { + test('Values are properly reflected', () { + double value = TreeViewIndentationType.standard.value; + expect(value, 10.0); + + value = TreeViewIndentationType.none.value; + expect(value, 0.0); + + value = TreeViewIndentationType.custom(50.0).value; + expect(value, 50.0); + }); + }); +} diff --git a/packages/two_dimensional_scrollables/test/tree_view/tree_delegate_test.dart b/packages/two_dimensional_scrollables/test/tree_view/tree_delegate_test.dart new file mode 100644 index 00000000000..139a153d939 --- /dev/null +++ b/packages/two_dimensional_scrollables/test/tree_view/tree_delegate_test.dart @@ -0,0 +1,87 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +const TreeRow span = TreeRow(extent: FixedTreeRowExtent(50)); + +void main() { + test('TreeVicinity converts ChildVicinity', () { + const TreeVicinity vicinity = TreeVicinity(depth: 5, row: 10); + expect(vicinity.xIndex, 5); + expect(vicinity.yIndex, 10); + expect(vicinity.row, 10); + expect(vicinity.depth, 5); + expect(vicinity.toString(), '(row: 10, depth: 5)'); + }); + + group('TreeRowBuilderDelegate', () { + test('exposes addAutomaticKeepAlives from super class', () { + final TreeRowBuilderDelegate delegate = TreeRowBuilderDelegate( + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => span, + rowCount: 6, + addAutomaticKeepAlives: false, + ); + expect(delegate.addAutomaticKeepAlives, isFalse); + }); + + test('asserts valid counts for rows', () { + TreeRowBuilderDelegate? delegate; + expect( + () { + delegate = TreeRowBuilderDelegate( + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => span, + rowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('rowCount >= 0'), + ), + ), + ); + + expect(delegate, isNull); + }); + + test('sets max y index (not x) of super class', () { + final TreeRowBuilderDelegate delegate = TreeRowBuilderDelegate( + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => span, + rowCount: 6, + ); + expect(delegate.maxYIndex, 5); // rows + expect(delegate.maxXIndex, isNull); // unknown max depth + }); + + test('Notifies listeners & rebuilds', () { + bool notified = false; + TreeRowBuilderDelegate oldDelegate; + + final TreeRowBuilderDelegate delegate = TreeRowBuilderDelegate( + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => span, + rowCount: 6, + ); + delegate.addListener(() { + notified = true; + }); + + // change row count + oldDelegate = delegate; + delegate.rowCount = 7; + expect(notified, isTrue); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // Builder delegate always returns true. + expect(delegate.shouldRebuild(delegate), isTrue); + }); + }); +} diff --git a/packages/two_dimensional_scrollables/test/tree_view/tree_span_test.dart b/packages/two_dimensional_scrollables/test/tree_view/tree_span_test.dart new file mode 100644 index 00000000000..0ccf49297e4 --- /dev/null +++ b/packages/two_dimensional_scrollables/test/tree_view/tree_span_test.dart @@ -0,0 +1,208 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +void main() { + group('TreeRowExtent', () { + test('FixedTreeRowExtent', () { + FixedTreeRowExtent extent = const FixedTreeRowExtent(150); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 150, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 150, + ); + // asserts value is valid + expect( + () { + extent = FixedTreeRowExtent(-100); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pixels >= 0.0'), + ), + ), + ); + }); + + test('FractionalTreeRowExtent', () { + FractionalTreeRowExtent extent = const FractionalTreeRowExtent(0.5); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 0.0, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 500, + ); + // asserts value is valid + expect( + () { + extent = FractionalTreeRowExtent(-20); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('fraction >= 0.0'), + ), + ), + ); + }); + + test('RemainingTreeRowExtent', () { + const RemainingTreeRowExtent extent = RemainingTreeRowExtent(); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 0.0, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 900, + ); + }); + + test('CombiningTreeRowExtent', () { + final CombiningTreeRowExtent extent = CombiningTreeRowExtent( + const FixedTreeRowExtent(100), + const RemainingTreeRowExtent(), + (double a, double b) { + return a + b; + }, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 100, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 1000, + ); + }); + + test('MaxTreeRowExtent', () { + const MaxTreeRowExtent extent = MaxTreeRowExtent( + FixedTreeRowExtent(100), + RemainingTreeRowExtent(), + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 100, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 900, + ); + }); + + test('MinTreeRowExtent', () { + const MinTreeRowExtent extent = MinTreeRowExtent( + FixedTreeRowExtent(100), + RemainingTreeRowExtent(), + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 0, + ); + expect( + extent.calculateExtent( + const TreeRowExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 100, + ); + }); + }); + + test('TreeRowDecoration', () { + TreeRowDecoration decoration = const TreeRowDecoration( + color: Color(0xffff0000), + ); + final TestCanvas canvas = TestCanvas(); + const Rect rect = Rect.fromLTWH(0, 0, 10, 10); + final TreeRowDecorationPaintDetails details = TreeRowDecorationPaintDetails( + canvas: canvas, + 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)); + expect(canvas.paint.isAntiAlias, isFalse); + final TestTreeRowBorder border = TestTreeRowBorder( + top: const BorderSide(), + ); + decoration = TreeRowDecoration( + border: border, + borderRadius: radius, + ); + decoration.paint(details); + expect(border.details, details); + expect(border.radius, radius); + }); +} + +class TestCanvas implements Canvas { + final List noSuchMethodInvocations = []; + late Rect rect; + late Paint paint; + + @override + void drawRect(Rect rect, Paint paint) { + this.rect = rect; + this.paint = paint; + } + + @override + void noSuchMethod(Invocation invocation) { + noSuchMethodInvocations.add(invocation); + } +} + +class TestTreeRowBorder extends TreeRowBorder { + TestTreeRowBorder({super.top}); + TreeRowDecorationPaintDetails? details; + BorderRadius? radius; + @override + void paint(TreeRowDecorationPaintDetails details, BorderRadius? radius) { + this.details = details; + this.radius = radius; + } +} diff --git a/packages/two_dimensional_scrollables/test/tree_view/tree_test.dart b/packages/two_dimensional_scrollables/test/tree_view/tree_test.dart new file mode 100644 index 00000000000..a1790879fe9 --- /dev/null +++ b/packages/two_dimensional_scrollables/test/tree_view/tree_test.dart @@ -0,0 +1,824 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +List> simpleNodeSet = >[ + TreeViewNode('Root 0'), + TreeViewNode( + 'Root 1', + expanded: true, + children: >[ + TreeViewNode('Child 1:0'), + TreeViewNode('Child 1:1'), + ], + ), + TreeViewNode( + 'Root 2', + children: >[ + TreeViewNode('Child 2:0'), + TreeViewNode('Child 2:1'), + ], + ), + TreeViewNode('Root 3'), +]; + +void main() { + group('TreeViewNode', () { + test('getters, toString', () { + final List> children = >[ + TreeViewNode('child'), + ]; + final TreeViewNode node = TreeViewNode( + 'parent', + children: children, + expanded: true, + ); + expect(node.content, 'parent'); + expect(node.children, children); + expect(node.isExpanded, isTrue); + expect(node.children.first.content, 'child'); + expect(node.children.first.children.isEmpty, isTrue); + expect(node.children.first.isExpanded, isFalse); + // Set by TreeView when built for tree integrity + expect(node.depth, isNull); + expect(node.parent, isNull); + expect(node.children.first.depth, isNull); + expect(node.children.first.parent, isNull); + + expect( + node.toString(), + 'TreeViewNode: parent, depth: null, parent, expanded: true', + ); + expect( + node.children.first.toString(), + 'TreeViewNode: child, depth: null, leaf', + ); + }); + + testWidgets('TreeView sets ups parent and depth properties', + (WidgetTester tester) async { + final List> children = >[ + TreeViewNode('child'), + ]; + final TreeViewNode node = TreeViewNode( + 'parent', + children: children, + expanded: true, + ); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: >[node], + ), + )); + expect(node.content, 'parent'); + expect(node.children, children); + expect(node.isExpanded, isTrue); + expect(node.children.first.content, 'child'); + expect(node.children.first.children.isEmpty, isTrue); + expect(node.children.first.isExpanded, isFalse); + // Set by TreeView when built for tree integrity + expect(node.depth, 0); + expect(node.parent, isNull); + expect(node.children.first.depth, 1); + expect(node.children.first.parent, node); + + expect( + node.toString(), + 'TreeViewNode: parent, depth: root, parent, expanded: true', + ); + expect( + node.children.first.toString(), + 'TreeViewNode: child, depth: 1, leaf', + ); + }); + }); + + group('TreeViewController', () { + setUp(() { + // Reset node conditions for each test. + simpleNodeSet = >[ + TreeViewNode('Root 0'), + TreeViewNode( + 'Root 1', + expanded: true, + children: >[ + TreeViewNode('Child 1:0'), + TreeViewNode('Child 1:1'), + ], + ), + TreeViewNode( + 'Root 2', + children: >[ + TreeViewNode('Child 2:0'), + TreeViewNode('Child 2:1'), + ], + ), + TreeViewNode('Root 3'), + ]; + }); + testWidgets('Can set controller on TreeView', (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + TreeViewController? returnedController; + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + returnedController ??= TreeViewController.of(context); + return TreeView.defaultTreeNodeBuilder( + context, + node, + toggleAnimationStyle, + ); + }, + ), + )); + expect(controller, returnedController); + }); + + testWidgets('Can get default controller on TreeView', + (WidgetTester tester) async { + TreeViewController? returnedController; + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + returnedController ??= TreeViewController.maybeOf(context); + return TreeView.defaultTreeNodeBuilder( + context, + node, + toggleAnimationStyle, + ); + }, + ), + )); + expect(returnedController, isNotNull); + }); + + testWidgets('Can get node for TreeViewNode.content', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + )); + + expect(controller.getNodeFor('Root 0'), simpleNodeSet[0]); + }); + + testWidgets('Can get isExpanded for a node', (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + )); + expect( + controller.isExpanded(simpleNodeSet[0]), + isFalse, + ); + expect( + controller.isExpanded(simpleNodeSet[1]), + isTrue, + ); + }); + + testWidgets('Can get isActive for a node', (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + )); + expect( + controller.isActive(simpleNodeSet[0]), + isTrue, + ); + expect( + controller.isActive(simpleNodeSet[1]), + isTrue, + ); + // The parent 'Root 2' is not expanded, so its children are not active. + expect( + controller.isExpanded(simpleNodeSet[2]), + isFalse, + ); + expect( + controller.isActive(simpleNodeSet[2].children[0]), + isFalse, + ); + }); + + testWidgets('Can toggleNode, to collapse or expand', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + )); + + // The parent 'Root 2' is not expanded, so its children are not active. + expect( + controller.isExpanded(simpleNodeSet[2]), + isFalse, + ); + expect( + controller.isActive(simpleNodeSet[2].children[0]), + isFalse, + ); + // Toggle 'Root 2' to expand it + controller.toggleNode(simpleNodeSet[2]); + expect( + controller.isExpanded(simpleNodeSet[2]), + isTrue, + ); + expect( + controller.isActive(simpleNodeSet[2].children[0]), + isTrue, + ); + + // The parent 'Root 1' is expanded, so its children are active. + expect( + controller.isExpanded(simpleNodeSet[1]), + isTrue, + ); + expect( + controller.isActive(simpleNodeSet[1].children[0]), + isTrue, + ); + // Collapse 'Root 1' + controller.toggleNode(simpleNodeSet[1]); + expect( + controller.isExpanded(simpleNodeSet[1]), + isFalse, + ); + expect( + controller.isActive(simpleNodeSet[1].children[0]), + isTrue, + ); + // Nodes are not removed from the active list until the collapse animation + // completes. The parent's expansions status also does not change until the + // animation completes. + await tester.pumpAndSettle(); + expect( + controller.isExpanded(simpleNodeSet[1]), + isFalse, + ); + expect( + controller.isActive(simpleNodeSet[1].children[0]), + isFalse, + ); + }); + + testWidgets('Can expandNode, then collapseAll', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + )); + + // The parent 'Root 2' is not expanded, so its children are not active. + expect( + controller.isExpanded(simpleNodeSet[2]), + isFalse, + ); + expect( + controller.isActive(simpleNodeSet[2].children[0]), + isFalse, + ); + // Expand 'Root 2' + controller.expandNode(simpleNodeSet[2]); + expect( + controller.isExpanded(simpleNodeSet[2]), + isTrue, + ); + expect( + controller.isActive(simpleNodeSet[2].children[0]), + isTrue, + ); + + // Both parents from our simple node set are expanded. + // 'Root 1' + expect(controller.isExpanded(simpleNodeSet[1]), isTrue); + // 'Root 2' + expect(controller.isExpanded(simpleNodeSet[2]), isTrue); + // Collapse both. + controller.collapseAll(); + await tester.pumpAndSettle(); + // Both parents from our simple node set have collapsed. + // 'Root 1' + expect(controller.isExpanded(simpleNodeSet[1]), isFalse); + // 'Root 2' + expect(controller.isExpanded(simpleNodeSet[2]), isFalse); + }); + + testWidgets('Can collapseNode, then expandAll', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + )); + + // The parent 'Root 1' is expanded, so its children are active. + expect( + controller.isExpanded(simpleNodeSet[1]), + isTrue, + ); + expect( + controller.isActive(simpleNodeSet[1].children[0]), + isTrue, + ); + // Collapse 'Root 1' + controller.collapseNode(simpleNodeSet[1]); + expect( + controller.isExpanded(simpleNodeSet[1]), + isFalse, + ); + expect( + controller.isActive(simpleNodeSet[1].children[0]), + isTrue, + ); + // Nodes are not removed from the active list until the collapse animation + // completes. + await tester.pumpAndSettle(); + expect( + controller.isActive(simpleNodeSet[1].children[0]), + isFalse, + ); + + // Both parents from our simple node set are collapsed. + // 'Root 1' + expect(controller.isExpanded(simpleNodeSet[1]), isFalse); + // 'Root 2' + expect(controller.isExpanded(simpleNodeSet[2]), isFalse); + // Expand both. + controller.expandAll(); + // Both parents from our simple node set are expanded. + // 'Root 1' + expect(controller.isExpanded(simpleNodeSet[1]), isTrue); + // 'Root 2' + expect(controller.isExpanded(simpleNodeSet[2]), isTrue); + }); + }); + + group('TreeView', () { + setUp(() { + // Reset node conditions for each test. + simpleNodeSet = >[ + TreeViewNode('Root 0'), + TreeViewNode( + 'Root 1', + expanded: true, + children: >[ + TreeViewNode('Child 1:0'), + TreeViewNode('Child 1:1'), + ], + ), + TreeViewNode( + 'Root 2', + children: >[ + TreeViewNode('Child 2:0'), + TreeViewNode('Child 2:1'), + ], + ), + TreeViewNode('Root 3'), + ]; + }); + test('asserts proper axis directions', () { + TreeView? treeView; + expect( + () { + treeView = TreeView( + tree: simpleNodeSet, + verticalDetails: const ScrollableDetails.vertical(reverse: true), + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('verticalDetails.direction == AxisDirection.down'), + ), + ), + ); + expect( + () { + treeView = TreeView( + tree: simpleNodeSet, + horizontalDetails: + const ScrollableDetails.horizontal(reverse: true), + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('horizontalDetails.direction == AxisDirection.right'), + ), + ), + ); + expect(treeView, isNull); + }); + + testWidgets('.toggleNodeWith, onNodeToggle', (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + // The default node builder wraps the leading icon with toggleNodeWith. + bool toggled = false; + TreeViewNode? toggledNode; + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + onNodeToggle: (TreeViewNode node) { + toggled = true; + toggledNode = node; + }, + ), + )); + expect(controller.isExpanded(simpleNodeSet[1]), isTrue); + await tester.tap(find.byType(Icon).first); + await tester.pump(); + expect(controller.isExpanded(simpleNodeSet[1]), isFalse); + expect(toggled, isTrue); + expect(toggledNode, simpleNodeSet[1]); + await tester.pumpAndSettle(); + expect(controller.isExpanded(simpleNodeSet[1]), isFalse); + toggled = false; + toggledNode = null; + + // Use toggleNodeWith to make the whole row trigger the node state. + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + controller: controller, + onNodeToggle: (TreeViewNode node) { + toggled = true; + toggledNode = node; + }, + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + final Duration animationDuration = toggleAnimationStyle.duration ?? + TreeView.defaultAnimationDuration; + final Curve animationCurve = + toggleAnimationStyle.curve ?? TreeView.defaultAnimationCurve; + // This makes the whole row trigger toggling. + return TreeView.wrapChildToToggleNode( + node: node, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row(children: [ + // Icon for parent nodes + SizedBox.square( + dimension: 30.0, + child: node.children.isNotEmpty + ? AnimatedRotation( + turns: node.isExpanded ? 0.25 : 0.0, + duration: animationDuration, + curve: animationCurve, + child: const Icon(IconData(0x25BA), size: 14), + ) + : null, + ), + // Spacer + const SizedBox(width: 8.0), + // Content + Text(node.content), + ]), + ), + ); + }, + ), + )); + // Still collapsed from earlier + expect(controller.isExpanded(simpleNodeSet[1]), isFalse); + // Tapping on the text instead of the Icon. + await tester.tap(find.text('Root 1')); + await tester.pump(); + expect(controller.isExpanded(simpleNodeSet[1]), isTrue); + expect(toggled, isTrue); + expect(toggledNode, simpleNodeSet[1]); + }); + + testWidgets('AnimationStyle is piped through to node builder', + (WidgetTester tester) async { + AnimationStyle? style; + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + style ??= toggleAnimationStyle; + return Text(node.content); + }, + ), + )); + // Default + expect( + style, + AnimationStyle( + duration: TreeView.defaultAnimationDuration, + curve: TreeView.defaultAnimationCurve, + ), + ); + + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + toggleAnimationStyle: AnimationStyle.noAnimation, + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + style = toggleAnimationStyle; + return Text(node.content); + }, + ), + )); + expect(style, isNotNull); + expect(style!.curve, isNull); + expect(style!.duration, Duration.zero); + style = null; + + await tester.pumpWidget(MaterialApp( + home: TreeView( + tree: simpleNodeSet, + toggleAnimationStyle: AnimationStyle( + curve: Curves.easeIn, + duration: const Duration(milliseconds: 200), + ), + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle toggleAnimationStyle, + ) { + style ??= toggleAnimationStyle; + return Text(node.content); + }, + ), + )); + expect(style, isNotNull); + expect(style!.curve, Curves.easeIn); + expect(style!.duration, const Duration(milliseconds: 200)); + }); + + testWidgets('Adding more root TreeViewNodes are reflected in the tree', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Scaffold( + body: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + setState(() { + simpleNodeSet.add(TreeViewNode('Added root')); + }); + }, + ), + ); + }, + ), + )); + await tester.pump(); + + expect(find.text('Root 0'), findsOneWidget); + expect(find.text('Root 1'), findsOneWidget); + expect(find.text('Child 1:0'), findsOneWidget); + expect(find.text('Child 1:1'), findsOneWidget); + expect(find.text('Root 2'), findsOneWidget); + expect(find.text('Child 2:0'), findsNothing); + expect(find.text('Child 2:1'), findsNothing); + expect(find.text('Root 3'), findsOneWidget); + expect(find.text('Added root'), findsNothing); + + await tester.tap(find.byType(FloatingActionButton)); + await tester.pump(); + + expect(find.text('Root 0'), findsOneWidget); + expect(find.text('Root 1'), findsOneWidget); + expect(find.text('Child 1:0'), findsOneWidget); + expect(find.text('Child 1:1'), findsOneWidget); + expect(find.text('Root 2'), findsOneWidget); + expect(find.text('Child 2:0'), findsNothing); + expect(find.text('Child 2:1'), findsNothing); + expect(find.text('Root 3'), findsOneWidget); + // Node was added + expect(find.text('Added root'), findsOneWidget); + }); + + testWidgets( + 'Adding more TreeViewNodes below the root are reflected in the tree', + (WidgetTester tester) async { + final TreeViewController controller = TreeViewController(); + await tester.pumpWidget(MaterialApp( + home: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Scaffold( + body: TreeView( + tree: simpleNodeSet, + controller: controller, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + setState(() { + simpleNodeSet[1].children.add( + TreeViewNode('Added child'), + ); + }); + }, + ), + ); + }, + ), + )); + await tester.pump(); + + expect(find.text('Root 0'), findsOneWidget); + expect(find.text('Root 1'), findsOneWidget); + expect(find.text('Child 1:0'), findsOneWidget); + expect(find.text('Child 1:1'), findsOneWidget); + expect(find.text('Added child'), findsNothing); + expect(find.text('Root 2'), findsOneWidget); + expect(find.text('Child 2:0'), findsNothing); + expect(find.text('Child 2:1'), findsNothing); + expect(find.text('Root 3'), findsOneWidget); + + await tester.tap(find.byType(FloatingActionButton)); + await tester.pump(); + + expect(find.text('Root 0'), findsOneWidget); + expect(find.text('Root 1'), findsOneWidget); + expect(find.text('Child 1:0'), findsOneWidget); + expect(find.text('Child 1:1'), findsOneWidget); + // Child node was added + expect(find.text('Added child'), findsOneWidget); + expect(find.text('Root 2'), findsOneWidget); + expect(find.text('Child 2:0'), findsNothing); + expect(find.text('Child 2:1'), findsNothing); + expect(find.text('Root 3'), findsOneWidget); + }); + + test('should use the generic type for callbacks and builders', () { + final TreeView treeView = TreeView( + tree: simpleNodeSet, + treeNodeBuilder: ( + BuildContext context, + TreeViewNode node, + AnimationStyle animationStyle, + ) { + return TreeView.defaultTreeNodeBuilder( + context, + node, + animationStyle, + ); + }, + treeRowBuilder: (TreeViewNode node) { + return TreeView.defaultTreeRowBuilder(node); + }, + onNodeToggle: (TreeViewNode node) {}, + ); + + expect(treeView.onNodeToggle, isA>()); + expect(treeView.treeNodeBuilder, isA>()); + expect(treeView.treeRowBuilder, isA>()); + }); + }); + + group('TreeViewport', () { + test('asserts proper axis directions', () { + TreeViewport? treeViewport; + expect( + () { + treeViewport = TreeViewport( + verticalOffset: TestOffset(), + verticalAxisDirection: AxisDirection.up, + horizontalOffset: TestOffset(), + horizontalAxisDirection: AxisDirection.right, + delegate: TreeRowBuilderDelegate( + rowCount: 0, + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => const TreeRow( + extent: FixedTreeRowExtent(40.0), + ), + ), + activeAnimations: const {}, + rowDepths: const {}, + indentation: 0.0, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('verticalAxisDirection == AxisDirection.down'), + ), + ), + ); + expect( + () { + treeViewport = TreeViewport( + verticalOffset: TestOffset(), + verticalAxisDirection: AxisDirection.down, + horizontalOffset: TestOffset(), + horizontalAxisDirection: AxisDirection.left, + delegate: TreeRowBuilderDelegate( + rowCount: 0, + nodeBuilder: (_, __) => const SizedBox(), + rowBuilder: (_) => const TreeRow( + extent: FixedTreeRowExtent(40.0), + ), + ), + activeAnimations: const {}, + rowDepths: const {}, + indentation: 0.0, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('horizontalAxisDirection == AxisDirection.right'), + ), + ), + ); + expect(treeViewport, isNull); + }); + }); +} + +class TestOffset extends ViewportOffset { + TestOffset(); + + @override + bool get allowImplicitScrolling => throw UnimplementedError(); + + @override + Future animateTo( + double to, { + required Duration duration, + required Curve curve, + }) { + throw UnimplementedError(); + } + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + throw UnimplementedError(); + } + + @override + bool applyViewportDimension(double viewportDimension) { + throw UnimplementedError(); + } + + @override + void correctBy(double correction) {} + + @override + bool get hasPixels => throw UnimplementedError(); + + @override + void jumpTo(double pixels) {} + + @override + double get pixels => throw UnimplementedError(); + + @override + ScrollDirection get userScrollDirection => throw UnimplementedError(); +} diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index ea11aba6df7..9ef34c528aa 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.3.0 + +* Adds `BrowserConfiguration` parameter, to configure in-app browser views, such as Android Custom Tabs or SFSafariViewController. +* Adds `showTitle` to `BrowserConfiguration`, to allow showing webpage titles in in-app browser views. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 6.2.6 * Updates minimum iOS implementation version to include a privacy manifest. diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index f79c7ba6f22..5955540491e 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -10,11 +10,7 @@ A Flutter plugin for launching a URL. |-------------|---------|-------|-------|--------|-----|-------------| | **Support** | SDK 16+ | 12.0+ | Any | 10.14+ | Any | Windows 10+ | -## Usage - -To use this plugin, add `url_launcher` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/). - -### Example +## Example ```dart diff --git a/packages/url_launcher/url_launcher/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/url_launcher/url_launcher/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/url_launcher/url_launcher/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/url_launcher/url_launcher/example/android/build.gradle b/packages/url_launcher/url_launcher/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/url_launcher/url_launcher/example/android/build.gradle +++ b/packages/url_launcher/url_launcher/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/url_launcher/url_launcher/example/android/settings.gradle b/packages/url_launcher/url_launcher/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/url_launcher/url_launcher/example/android/settings.gradle +++ b/packages/url_launcher/url_launcher/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index 40307a3f715..e3a353f81ed 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -74,6 +74,16 @@ class _MyHomePageState extends State { } } + Future _launchInAppWithBrowserOptions(Uri url) async { + if (!await launchUrl( + url, + mode: LaunchMode.inAppBrowserView, + browserConfiguration: const BrowserConfiguration(showTitle: true), + )) { + throw Exception('Could not launch $url'); + } + } + Future _launchAsInAppWebViewWithCustomHeaders(Uri url) async { if (!await launchUrl( url, @@ -220,6 +230,13 @@ class _MyHomePageState extends State { child: const Text('Launch in app + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInAppWithBrowserOptions(toLaunch); + }), + child: const Text('Launch in app with title displayed'), + ), + const Padding(padding: EdgeInsets.all(16.0)), Link( uri: Uri.parse( 'https://pub.dev/documentation/url_launcher/latest/link/link-library.html'), diff --git a/packages/url_launcher/url_launcher/example/macos/Runner/DebugProfile.entitlements b/packages/url_launcher/url_launcher/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/url_launcher/url_launcher/example/macos/Runner/DebugProfile.entitlements +++ b/packages/url_launcher/url_launcher/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/url_launcher/url_launcher/example/macos/Runner/Release.entitlements b/packages/url_launcher/url_launcher/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/url_launcher/url_launcher/example/macos/Runner/Release.entitlements +++ b/packages/url_launcher/url_launcher/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index 85f20247e32..50bfbc8c3b8 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.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: 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 3169e25dfa7..0e27da35fe7 100644 --- a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart +++ b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart @@ -8,7 +8,8 @@ import 'types.dart'; /// Converts an (app-facing) [WebViewConfiguration] to a (platform interface) /// [InAppWebViewConfiguration]. -InAppWebViewConfiguration convertConfiguration(WebViewConfiguration config) { +InAppWebViewConfiguration convertWebViewConfiguration( + WebViewConfiguration config) { return InAppWebViewConfiguration( enableJavaScript: config.enableJavaScript, enableDomStorage: config.enableDomStorage, @@ -16,6 +17,15 @@ InAppWebViewConfiguration convertConfiguration(WebViewConfiguration config) { ); } +/// Converts an (app-facing) [BrowserConfiguration] to a (platform interface) +/// [InAppBrowserConfiguration]. +InAppBrowserConfiguration convertBrowserConfiguration( + BrowserConfiguration config) { + return InAppBrowserConfiguration( + showTitle: config.showTitle, + ); +} + /// Converts an (app-facing) [LaunchMode] to a (platform interface) /// [PreferredLaunchMode]. PreferredLaunchMode convertLaunchMode(LaunchMode mode) { diff --git a/packages/url_launcher/url_launcher/lib/src/types.dart b/packages/url_launcher/url_launcher/lib/src/types.dart index 2bf56e1b5d8..c8df68faadb 100644 --- a/packages/url_launcher/url_launcher/lib/src/types.dart +++ b/packages/url_launcher/url_launcher/lib/src/types.dart @@ -55,3 +55,15 @@ class WebViewConfiguration { /// Not all browsers support this, so it is not guaranteed to be honored. final Map headers; } + +/// Additional configuration options for [LaunchMode.inAppBrowserView] +@immutable +class BrowserConfiguration { + /// Creates a new InAppBrowserConfiguration with given settings. + const BrowserConfiguration({this.showTitle = false}); + + /// Whether or not to show the webpage title. + /// + /// May not be supported on all platforms. + final bool showTitle; +} 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 ca0bcc6109d..bf878c60dde 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 @@ -23,6 +23,7 @@ Future launchUrlString( String urlString, { LaunchMode mode = LaunchMode.platformDefault, WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), + BrowserConfiguration browserConfiguration = const BrowserConfiguration(), String? webOnlyWindowName, }) async { if ((mode == LaunchMode.inAppWebView || @@ -35,7 +36,8 @@ Future launchUrlString( urlString, LaunchOptions( mode: convertLaunchMode(mode), - webViewConfiguration: convertConfiguration(webViewConfiguration), + webViewConfiguration: convertWebViewConfiguration(webViewConfiguration), + browserConfiguration: convertBrowserConfiguration(browserConfiguration), webOnlyWindowName: webOnlyWindowName, ), ); 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 bc55eb2b656..97f8af482cd 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 @@ -40,6 +40,7 @@ Future launchUrl( Uri url, { LaunchMode mode = LaunchMode.platformDefault, WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), + BrowserConfiguration browserConfiguration = const BrowserConfiguration(), String? webOnlyWindowName, }) async { if ((mode == LaunchMode.inAppWebView || @@ -52,7 +53,8 @@ Future launchUrl( url.toString(), LaunchOptions( mode: convertLaunchMode(mode), - webViewConfiguration: convertConfiguration(webViewConfiguration), + webViewConfiguration: convertWebViewConfiguration(webViewConfiguration), + browserConfiguration: convertBrowserConfiguration(browserConfiguration), webOnlyWindowName: webOnlyWindowName, ), ); diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index a5ad397ae84..06b2fab2aa2 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.6 +version: 6.3.0 environment: sdk: ">=3.2.0 <4.0.0" @@ -28,13 +28,13 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_android: ^6.2.0 + url_launcher_android: ^6.3.0 url_launcher_ios: ^6.2.4 # Allow either the pure-native or Dart/native hybrid versions of the desktop # implementations, as both are compatible. url_launcher_linux: ^3.1.0 url_launcher_macos: ^3.1.0 - url_launcher_platform_interface: ^2.2.0 + url_launcher_platform_interface: ^2.3.0 url_launcher_web: ^2.2.0 url_launcher_windows: ^3.1.0 diff --git a/packages/url_launcher/url_launcher/test/link_test.dart b/packages/url_launcher/url_launcher/test/link_test.dart index 052ca2556e3..4d8fed20797 100644 --- a/packages/url_launcher/url_launcher/test/link_test.dart +++ b/packages/url_launcher/url_launcher/test/link_test.dart @@ -61,6 +61,7 @@ void main() { enableDomStorage: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); await followLink!(); @@ -92,6 +93,7 @@ void main() { enableDomStorage: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); await followLink!(); 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 fc0181d4a4e..f964d537107 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 @@ -17,6 +17,7 @@ class MockUrlLauncher extends Fake bool? enableJavaScript; bool? enableDomStorage; bool? universalLinksOnly; + bool? showTitle; Map? headers; String? webOnlyWindowName; @@ -41,6 +42,7 @@ class MockUrlLauncher extends Fake required bool universalLinksOnly, required Map headers, required String? webOnlyWindowName, + required bool showTitle, }) { this.url = url; this.launchMode = launchMode; @@ -51,6 +53,7 @@ class MockUrlLauncher extends Fake this.universalLinksOnly = universalLinksOnly; this.headers = headers; this.webOnlyWindowName = webOnlyWindowName; + this.showTitle = showTitle; } // ignore: use_setters_to_change_properties @@ -87,6 +90,7 @@ class MockUrlLauncher extends Fake expect(universalLinksOnly, this.universalLinksOnly); expect(headers, this.headers); expect(webOnlyWindowName, this.webOnlyWindowName); + expect(webOnlyWindowName, this.webOnlyWindowName); launchCalled = true; return response!; } @@ -98,6 +102,7 @@ class MockUrlLauncher extends Fake expect(options.webViewConfiguration.enableJavaScript, enableJavaScript); expect(options.webViewConfiguration.enableDomStorage, enableDomStorage); expect(options.webViewConfiguration.headers, headers); + expect(options.browserConfiguration.showTitle, showTitle); expect(options.webOnlyWindowName, webOnlyWindowName); launchCalled = true; return response!; diff --git a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart index 091f9b7c8fd..8dee5efd9c8 100644 --- a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart +++ b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart @@ -53,6 +53,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/'), isTrue); @@ -69,6 +70,7 @@ void main() { universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -90,6 +92,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceSafariVC: true), isTrue); @@ -106,6 +109,7 @@ void main() { universalLinksOnly: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -125,6 +129,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceWebView: true), isTrue); @@ -141,6 +146,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -160,6 +166,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -179,6 +186,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceSafariVC: false), isTrue); @@ -200,6 +208,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('mailto:gmail-noreply@google.com?subject=Hello'), @@ -231,6 +240,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); @@ -263,6 +273,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); @@ -296,6 +307,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart index 64065ff99f9..0cd0f9b2eb5 100644 --- a/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart +++ b/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart @@ -49,6 +49,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -65,6 +66,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -81,6 +83,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -97,6 +100,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -113,6 +117,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString, mode: LaunchMode.inAppWebView), @@ -130,6 +135,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -138,6 +144,50 @@ void main() { isTrue); }); + test('in-app browser', () async { + const String urlString = 'https://flutter.dev'; + mock + ..setLaunchExpectations( + url: urlString, + launchMode: PreferredLaunchMode.inAppBrowserView, + enableJavaScript: true, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, + webOnlyWindowName: null, + showTitle: false, + ) + ..setResponse(true); + expect( + await launchUrlString(urlString, mode: LaunchMode.inAppBrowserView), + isTrue, + ); + }); + + test('in-app browser with title', () async { + const String urlString = 'https://flutter.dev'; + mock + ..setLaunchExpectations( + url: urlString, + launchMode: PreferredLaunchMode.inAppBrowserView, + enableJavaScript: true, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, + webOnlyWindowName: null, + showTitle: true, + ) + ..setResponse(true); + expect( + await launchUrlString( + urlString, + mode: LaunchMode.inAppBrowserView, + browserConfiguration: const BrowserConfiguration(showTitle: true), + ), + isTrue, + ); + }); + test('external non-browser only', () async { const String urlString = 'https://flutter.dev'; mock @@ -149,6 +199,7 @@ void main() { universalLinksOnly: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -168,6 +219,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -189,6 +241,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -210,6 +263,7 @@ void main() { universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -239,6 +293,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(emailLaunchUrlString), isTrue); @@ -257,6 +312,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); 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 0c5bfdcf247..15796ea659a 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 @@ -54,6 +54,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -70,6 +71,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -86,6 +88,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -102,6 +105,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -118,6 +122,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url, mode: LaunchMode.inAppWebView), isTrue); @@ -134,6 +139,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -151,6 +157,7 @@ void main() { universalLinksOnly: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -169,6 +176,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -179,6 +187,29 @@ void main() { isTrue); }); + test('in-app browser view with show title', () async { + final Uri url = Uri.parse('https://flutter.dev'); + mock + ..setLaunchExpectations( + url: url.toString(), + launchMode: PreferredLaunchMode.inAppBrowserView, + enableJavaScript: true, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, + webOnlyWindowName: null, + showTitle: true, + ) + ..setResponse(true); + expect( + await launchUrl( + url, + mode: LaunchMode.inAppBrowserView, + browserConfiguration: const BrowserConfiguration(showTitle: true), + ), + isTrue); + }); + test('in-app webview without DOM storage', () async { final Uri url = Uri.parse('https://flutter.dev'); mock @@ -190,6 +221,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -211,6 +243,7 @@ void main() { universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -243,6 +276,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(emailLaunchUrl), isTrue); diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index 8fd7a66a00b..73af3ff94af 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,12 @@ +## 6.3.3 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 6.3.2 + +* Bumps androidx.annotation:annotation from 1.7.1 to 1.8.0. + ## 6.3.1 * Updates minSdkVersion to 19. diff --git a/packages/url_launcher/url_launcher_android/README.md b/packages/url_launcher/url_launcher_android/README.md index 0a1a3c752ef..801a541a346 100644 --- a/packages/url_launcher/url_launcher_android/README.md +++ b/packages/url_launcher/url_launcher_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/url_launcher -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle b/packages/url_launcher/url_launcher_android/android/build.gradle index 5c799078ace..7604e9ac4b2 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.7.1' + implementation 'androidx.annotation:annotation:1.8.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/UrlLauncherPlugin.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java index f2160ec4df2..34bf08f9f30 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java @@ -20,21 +20,6 @@ public final class UrlLauncherPlugin implements FlutterPlugin, ActivityAware { private static final String TAG = "UrlLauncherPlugin"; @Nullable private UrlLauncher urlLauncher; - /** - * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} - * package. - * - *

Calling this automatically initializes the plugin. However plugins initialized this way - * won't react to changes in activity or context, unlike {@link UrlLauncherPlugin}. - */ - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - UrlLauncher handler = new UrlLauncher(registrar.context()); - handler.setActivity(registrar.activity()); - Messages.UrlLauncherApi.setup(registrar.messenger(), handler); - } - @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { urlLauncher = new UrlLauncher(binding.getApplicationContext()); diff --git a/packages/url_launcher/url_launcher_android/example/android/build.gradle b/packages/url_launcher/url_launcher_android/example/android/build.gradle index 40cbdf3c0de..0c2fe43da28 100644 --- a/packages/url_launcher/url_launcher_android/example/android/build.gradle +++ b/packages/url_launcher/url_launcher_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/url_launcher/url_launcher_android/example/android/settings.gradle b/packages/url_launcher/url_launcher_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/url_launcher/url_launcher_android/example/android/settings.gradle +++ b/packages/url_launcher/url_launcher_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/url_launcher/url_launcher_android/example/pubspec.yaml b/packages/url_launcher/url_launcher_android/example/pubspec.yaml index 90d59620b09..256861b7250 100644 --- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index b4c99cb101a..e3c65d4cd7a 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,10 +2,10 @@ 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.3.1 +version: 6.3.3 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index b5bb9c81481..0c6d13e0941 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.3.0 + +* Adds Swift Package Manager compatibility. + ## 6.2.5 * Adds explicit imports for UIKit. diff --git a/packages/url_launcher/url_launcher_ios/README.md b/packages/url_launcher/url_launcher_ios/README.md index 8b8b4fd447a..e76843293da 100644 --- a/packages/url_launcher/url_launcher_ios/README.md +++ b/packages/url_launcher/url_launcher_ios/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/url_launcher -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin 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 9dcf68c01f0..fa30175cb6b 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/**/*.swift' + s.source_files = 'url_launcher_ios/Sources/**/*.swift' s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', @@ -22,5 +22,5 @@ A Flutter plugin for making the underlying platform (Android or iOS) launch a UR s.dependency 'Flutter' s.platform = :ios, '12.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.resource_bundles = {'url_launcher_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'url_launcher_ios_privacy' => ['url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Package.swift b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Package.swift new file mode 100644 index 00000000000..5dbdd8ad4d7 --- /dev/null +++ b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "url_launcher_ios", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "url-launcher-ios", targets: ["url_launcher_ios"]) + ], + dependencies: [], + targets: [ + .target( + name: "url_launcher_ios", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/Launcher.swift similarity index 100% rename from packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift rename to packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/Launcher.swift diff --git a/packages/url_launcher/url_launcher_ios/ios/Resources/PrivacyInfo.xcprivacy b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/url_launcher/url_launcher_ios/ios/Resources/PrivacyInfo.xcprivacy rename to packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/URLLaunchSession.swift similarity index 100% rename from packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift rename to packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/URLLaunchSession.swift diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/URLLauncherPlugin.swift similarity index 100% rename from packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift rename to packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/URLLauncherPlugin.swift diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift similarity index 100% rename from packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift rename to packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift diff --git a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart index f5dc1052b32..c7097b41a74 100644 --- a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - swiftOut: 'ios/Classes/messages.g.swift', + swiftOut: 'ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift', copyrightHeader: 'pigeons/copyright.txt', )) diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index fd8c54619c5..65e9c204835 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.5 +version: 6.3.0 environment: sdk: ^3.2.3 diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index d9406f89be6..769924459a2 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 3.1.1 diff --git a/packages/url_launcher/url_launcher_linux/README.md b/packages/url_launcher/url_launcher_linux/README.md index ad7e9dbf0d9..143e1f3645a 100644 --- a/packages/url_launcher/url_launcher_linux/README.md +++ b/packages/url_launcher/url_launcher_linux/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/url_launcher -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml index 5b19e534014..eda0a3451e6 100644 --- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 75acf156783..cbc6013cd4e 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 3.1.1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index df9447ad852..f60deea990f 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 3.2.0 + +* Adds Swift Package Manager compatibility. * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. ## 3.1.0 diff --git a/packages/url_launcher/url_launcher_macos/README.md b/packages/url_launcher/url_launcher_macos/README.md index c164ddeecc4..adfcfcd8b0d 100644 --- a/packages/url_launcher/url_launcher_macos/README.md +++ b/packages/url_launcher/url_launcher_macos/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/url_launcher -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/url_launcher/url_launcher_macos/example/macos/Runner/DebugProfile.entitlements b/packages/url_launcher/url_launcher_macos/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..e635cd9a4bf 100644 --- a/packages/url_launcher/url_launcher_macos/example/macos/Runner/DebugProfile.entitlements +++ b/packages/url_launcher/url_launcher_macos/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/url_launcher/url_launcher_macos/example/macos/Runner/Release.entitlements b/packages/url_launcher/url_launcher_macos/example/macos/Runner/Release.entitlements index 852fa1a4728..0218c441b4e 100644 --- a/packages/url_launcher/url_launcher_macos/example/macos/Runner/Release.entitlements +++ b/packages/url_launcher/url_launcher_macos/example/macos/Runner/Release.entitlements @@ -3,6 +3,7 @@ com.apple.security.app-sandbox - + + diff --git a/packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/RunnerTests.swift index 622cffb3404..e58397e1e77 100644 --- a/packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/RunnerTests.swift @@ -7,6 +7,12 @@ import XCTest @testable import url_launcher_macos +// Tests whether NSURL parsing is strict. When linking against the macOS 14 SDK or later, +// NSURL uses a more lenient parser which will not return nil. +private func urlParsingIsStrict() -> Bool { + return URL(string: "b a d U R L") == nil +} + /// A stub to simulate the system Url handler. class StubWorkspace: SystemURLHandler { @@ -43,7 +49,11 @@ class RunnerTests: XCTestCase { let plugin = UrlLauncherPlugin() let result = try plugin.canLaunch(url: "invalid url") - XCTAssertEqual(result.error, .invalidUrl) + if urlParsingIsStrict() { + XCTAssertEqual(result.error, .invalidUrl) + } else { + XCTAssertFalse(result.value) + } } func testLaunchSuccessReturnsTrue() throws { @@ -69,6 +79,10 @@ class RunnerTests: XCTestCase { let plugin = UrlLauncherPlugin() let result = try plugin.launch(url: "invalid url") - XCTAssertEqual(result.error, .invalidUrl) + if urlParsingIsStrict() { + XCTAssertEqual(result.error, .invalidUrl) + } else { + XCTAssertFalse(result.value) + } } } diff --git a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml index 0ef6245dba2..6b3d81648ad 100644 --- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos.podspec b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos.podspec index 70864ec4f36..de18c66e7d0 100644 --- a/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos.podspec +++ b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos.podspec @@ -12,11 +12,10 @@ Pod::Spec.new do |s| s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos' } - s.source_files = 'Classes/**/*' + s.source_files = 'url_launcher_macos/Sources/url_launcher_macos/**/*.swift' s.dependency 'FlutterMacOS' s.platform = :osx, '10.14' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' end - diff --git a/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Package.swift b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Package.swift new file mode 100644 index 00000000000..13ddb82f941 --- /dev/null +++ b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +// 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 PackageDescription + +let package = Package( + name: "url_launcher_macos", + platforms: [ + .macOS("10.14") + ], + products: [ + .library(name: "url-launcher-macos", targets: ["url_launcher_macos"]) + ], + dependencies: [], + targets: [ + .target( + name: "url_launcher_macos", + dependencies: [], + resources: [ + .process("Resources") + ] + ) + ] +) diff --git a/packages/file_selector/file_selector_ios/ios/Assets/.gitkeep b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/Resources/.gitkeep similarity index 100% rename from packages/file_selector/file_selector_ios/ios/Assets/.gitkeep rename to packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/Resources/.gitkeep diff --git a/packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/UrlLauncherPlugin.swift similarity index 100% rename from packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift rename to packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/UrlLauncherPlugin.swift diff --git a/packages/url_launcher/url_launcher_macos/macos/Classes/messages.g.swift b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/messages.g.swift similarity index 100% rename from packages/url_launcher/url_launcher_macos/macos/Classes/messages.g.swift rename to packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/messages.g.swift diff --git a/packages/url_launcher/url_launcher_macos/pigeons/messages.dart b/packages/url_launcher/url_launcher_macos/pigeons/messages.dart index fd0027e6678..bd97c681d8e 100644 --- a/packages/url_launcher/url_launcher_macos/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_macos/pigeons/messages.dart @@ -6,7 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - swiftOut: 'macos/Classes/messages.g.swift', + swiftOut: + 'macos/url_launcher_macos/Sources/url_launcher_macos/messages.g.swift', copyrightHeader: 'pigeons/copyright.txt', )) diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 0a84ea92238..ab5a9250d40 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,11 +2,11 @@ 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.1.0 +version: 3.2.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index 1cab4de35f5..d4dd358a0ee 100644 --- a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.3.2 * Replaces deprecated RouteInformation API usage. diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index f0898e7fba5..20b7b2d7131 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.3.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_web/README.md b/packages/url_launcher/url_launcher_web/README.md index f186d7a49bb..91d7fb623b0 100644 --- a/packages/url_launcher/url_launcher_web/README.md +++ b/packages/url_launcher/url_launcher_web/README.md @@ -12,7 +12,7 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/url_launcher -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin ## Limitations on the Web platform diff --git a/packages/url_launcher/url_launcher_web/example/README.md b/packages/url_launcher/url_launcher_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/url_launcher/url_launcher_web/example/README.md +++ b/packages/url_launcher/url_launcher_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index 495780fde7e..978a224433d 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 3.1.1 diff --git a/packages/url_launcher/url_launcher_windows/README.md b/packages/url_launcher/url_launcher_windows/README.md index 4f10def5c4a..cc31e7e7cc9 100644 --- a/packages/url_launcher/url_launcher_windows/README.md +++ b/packages/url_launcher/url_launcher_windows/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/url_launcher -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml index e08f71f40f8..b6b6db36321 100644 --- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates the Windows implementation of the url_launcher plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index df2ec571feb..214806e482a 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 3.1.1 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index ea5a13da886..48b5e0b7eac 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.9.1 + +* Updates minimum web implementation version to ensure support for + the new `webOptions` exposed in `2.9.0`. + +## 2.9.0 + +* Exports types: `VideoPlayerWebOptions` and `VideoPlayerWebOptionsControls` to + customize the `webOptions` field in `VideoPlayerOptions` objects. +* Forwards `webOptions` to the web implementation. + +## 2.8.7 + +* Ensures that `value.position` never reports a value larger than `value.duration`. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.8.6 * Updates minimum iOS implementation version to include a privacy manifest. diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md index 55f4d2f614b..094bdd66699 100644 --- a/packages/video_player/video_player/README.md +++ b/packages/video_player/video_player/README.md @@ -12,9 +12,7 @@ A Flutter plugin for iOS, Android and Web for playing back video on a Widget sur ![The example app running in iOS](https://github.com/flutter/packages/blob/main/packages/video_player/video_player/doc/demo_ipod.gif?raw=true) -## Installation - -First, add `video_player` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). +## Setup ### iOS @@ -37,7 +35,7 @@ Android Manifest file, located in `/android/app/src/main/AndroidMa If you are using network-based videos, you will need to [add the `com.apple.security.network.client` -entitlement](https://docs.flutter.dev/platform-integration/macos/building#entitlements-and-the-app-sandbox) +entitlement](https://flutter.dev/to/macos-entitlements) ### Web diff --git a/packages/video_player/video_player/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/video_player/video_player/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/video_player/video_player/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/video_player/video_player/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/video_player/video_player/example/android/build.gradle b/packages/video_player/video_player/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/video_player/video_player/example/android/build.gradle +++ b/packages/video_player/video_player/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/video_player/video_player/example/android/settings.gradle b/packages/video_player/video_player/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/video_player/video_player/example/android/settings.gradle +++ b/packages/video_player/video_player/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/video_player/video_player/example/integration_test/video_player_test.dart b/packages/video_player/video_player/example/integration_test/video_player_test.dart index d475a1ac5ef..0acebb548c2 100644 --- a/packages/video_player/video_player/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player/example/integration_test/video_player_test.dart @@ -194,9 +194,11 @@ void main() { testWidgets('test video player view with local asset', (WidgetTester tester) async { + final Completer loaded = Completer(); Future started() async { await controller.initialize(); await controller.play(); + loaded.complete(); return true; } @@ -221,12 +223,12 @@ void main() { ), )); + await loaded.future; await tester.pumpAndSettle(); expect(controller.value.isPlaying, true); }, - skip: kIsWeb || // Web does not support local assets. - // Extremely flaky on iOS: https://github.com/flutter/flutter/issues/86915 - defaultTargetPlatform == TargetPlatform.iOS); + // Web does not support local assets. + skip: kIsWeb); }); group('file-based videos', () { diff --git a/packages/video_player/video_player/example/macos/Runner/DebugProfile.entitlements b/packages/video_player/video_player/example/macos/Runner/DebugProfile.entitlements index 3ba6c1266f2..c8f2dc5c736 100644 --- a/packages/video_player/video_player/example/macos/Runner/DebugProfile.entitlements +++ b/packages/video_player/video_player/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.client diff --git a/packages/video_player/video_player/example/macos/Runner/Release.entitlements b/packages/video_player/video_player/example/macos/Runner/Release.entitlements index ee95ab7e582..5fe13922aa5 100644 --- a/packages/video_player/video_player/example/macos/Runner/Release.entitlements +++ b/packages/video_player/video_player/example/macos/Runner/Release.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.network.client diff --git a/packages/video_player/video_player/example/pubspec.yaml b/packages/video_player/video_player/example/pubspec.yaml index acf127ff63a..ae536f3bd64 100644 --- a/packages/video_player/video_player/example/pubspec.yaml +++ b/packages/video_player/video_player/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the video_player plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 007c6ba14e5..c8acd6e969b 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -14,7 +14,13 @@ import 'package:video_player_platform_interface/video_player_platform_interface. import 'src/closed_caption_file.dart'; export 'package:video_player_platform_interface/video_player_platform_interface.dart' - show DataSourceType, DurationRange, VideoFormat, VideoPlayerOptions; + show + DataSourceType, + DurationRange, + VideoFormat, + VideoPlayerOptions, + VideoPlayerWebOptions, + VideoPlayerWebOptionsControls; export 'src/closed_caption_file.dart'; @@ -436,6 +442,14 @@ class VideoPlayerController extends ValueNotifier { _creatingCompleter!.complete(null); final Completer initializingCompleter = Completer(); + // Apply the web-specific options + if (kIsWeb && videoPlayerOptions?.webOptions != null) { + await _videoPlayerPlatform.setWebOptions( + _textureId, + videoPlayerOptions!.webOptions!, + ); + } + void eventListener(VideoEvent event) { if (_isDisposed) { return; @@ -743,6 +757,12 @@ class VideoPlayerController extends ValueNotifier { } void _updatePosition(Duration position) { + // The underlying native implementation on some platforms sometimes reports + // a position slightly past the reported max duration. Clamp to the duration + // to insulate clients from this behavior. + if (position > value.duration) { + position = value.duration; + } value = value.copyWith( position: position, caption: _getCaptionAt(position), diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 0dbdd0c9062..ecc85cfa25e 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,10 +3,10 @@ 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.6 +version: 2.9.1 environment: - sdk: ">=3.2.3 <4.0.0" + sdk: ^3.2.3 flutter: ">=3.16.6" flutter: @@ -27,8 +27,8 @@ dependencies: html: ^0.15.0 video_player_android: ^2.3.5 video_player_avfoundation: ^2.5.6 - video_player_platform_interface: ">=6.1.0 <7.0.0" - video_player_web: ^2.0.0 + video_player_platform_interface: ^6.2.0 + video_player_web: ^2.1.0 dev_dependencies: flutter_test: diff --git a/packages/video_player/video_player/test/video_player_initialization_test.dart b/packages/video_player/video_player/test/video_player_initialization_test.dart index 38cf71f67e8..5ecc7e4d69b 100644 --- a/packages/video_player/video_player/test/video_player_initialization_test.dart +++ b/packages/video_player/video_player/test/video_player_initialization_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:video_player/video_player.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; @@ -9,18 +10,51 @@ import 'package:video_player_platform_interface/video_player_platform_interface. import 'video_player_test.dart' show FakeVideoPlayerPlatform; void main() { - // This test needs to run first and therefore needs to be the only test - // in this file. - test('plugin initialized', () async { - TestWidgetsFlutterBinding.ensureInitialized(); - final FakeVideoPlayerPlatform fakeVideoPlayerPlatform = - FakeVideoPlayerPlatform(); - VideoPlayerPlatform.instance = fakeVideoPlayerPlatform; + TestWidgetsFlutterBinding.ensureInitialized(); + + late FakeVideoPlayerPlatform fakeVideoPlayerPlatform; + + setUp(() { + VideoPlayerPlatform.instance = + fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); + }); + test('plugin initialized', () async { final VideoPlayerController controller = VideoPlayerController.networkUrl( Uri.parse('https://127.0.0.1'), ); await controller.initialize(); expect(fakeVideoPlayerPlatform.calls.first, 'init'); }); + + test('web configuration is applied (web only)', () async { + const VideoPlayerWebOptions expected = VideoPlayerWebOptions( + allowContextMenu: false, + allowRemotePlayback: false, + controls: VideoPlayerWebOptionsControls.enabled(), + ); + + final VideoPlayerController controller = VideoPlayerController.networkUrl( + Uri.parse('https://127.0.0.1'), + videoPlayerOptions: VideoPlayerOptions( + webOptions: expected, + ), + ); + await controller.initialize(); + + expect( + () { + fakeVideoPlayerPlatform.calls.singleWhere( + (String call) => call == 'setWebOptions', + ); + }, + returnsNormally, + reason: 'setWebOptions must be called exactly once.', + ); + expect( + fakeVideoPlayerPlatform.webOptions[controller.textureId], + expected, + reason: 'web options must be passed to the platform', + ); + }, skip: !kIsWeb); } diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index c62615a06e4..f6eef244811 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -1311,6 +1311,8 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { bool forceInitError = false; int nextTextureId = 0; final Map _positions = {}; + final Map webOptions = + {}; @override Future create(DataSource dataSource) async { @@ -1392,4 +1394,14 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { Widget buildView(int textureId) { return Texture(textureId: textureId); } + + @override + Future setWebOptions( + int textureId, VideoPlayerWebOptions options) async { + if (!kIsWeb) { + throw UnimplementedError('setWebOptions() is only available in the web.'); + } + calls.add('setWebOptions'); + webOptions[textureId] = options; + } } diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 25a39fbdab3..863fcf80513 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,28 @@ +## 2.5.2 + +* Updates Android Gradle plugin to 8.5.0. + +## 2.5.1 + +* Removes additional references to the v1 Android embedding. + +## 2.5.0 + +* Migrates ExoPlayer to Media3-ExoPlayer 1.3.1. + +## 2.4.17 + +* Revert Impeller support. + +## 2.4.16 + +* [Supports Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). + +## 2.4.15 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + ## 2.4.14 * Calls `onDestroy` instead of `initialize` in onDetachedFromEngine. diff --git a/packages/video_player/video_player_android/README.md b/packages/video_player/video_player_android/README.md index 1495f48e30e..e33436b629d 100644 --- a/packages/video_player/video_player_android/README.md +++ b/packages/video_player/video_player_android/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/video_player -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/video_player/video_player_android/android/build.gradle b/packages/video_player/video_player_android/android/build.gradle index 9d4d8376de2..5675d385de3 100644 --- a/packages/video_player/video_player_android/android/build.gradle +++ b/packages/video_player/video_player_android/android/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:8.5.0' } } @@ -48,15 +48,16 @@ android { } dependencies { - def exoplayer_version = "2.18.7" - implementation "com.google.android.exoplayer:exoplayer-core:${exoplayer_version}" - implementation "com.google.android.exoplayer:exoplayer-hls:${exoplayer_version}" - implementation "com.google.android.exoplayer:exoplayer-dash:${exoplayer_version}" - implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:${exoplayer_version}" + def exoplayer_version = "1.3.1" + implementation "androidx.media3:media3-exoplayer:${exoplayer_version}" + implementation "androidx.media3:media3-exoplayer-hls:${exoplayer_version}" + implementation "androidx.media3:media3-exoplayer-dash:${exoplayer_version}" + implementation "androidx.media3:media3-exoplayer-smoothstreaming:${exoplayer_version}" testImplementation 'junit:junit:4.13.2' testImplementation 'androidx.test:core:1.3.0' testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'org.robolectric:robolectric:4.10.3' + testImplementation "androidx.media3:media3-test-utils:1.3.1" } testOptions { diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java new file mode 100644 index 00000000000..0940cc8b322 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java @@ -0,0 +1,101 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import androidx.annotation.NonNull; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.Player; +import androidx.media3.common.VideoSize; +import androidx.media3.exoplayer.ExoPlayer; + +final class ExoPlayerEventListener implements Player.Listener { + private final ExoPlayer exoPlayer; + private final VideoPlayerCallbacks events; + private boolean isBuffering = false; + private boolean isInitialized = false; + + ExoPlayerEventListener(ExoPlayer exoPlayer, VideoPlayerCallbacks events) { + this.exoPlayer = exoPlayer; + this.events = events; + } + + private void setBuffering(boolean buffering) { + if (isBuffering == buffering) { + return; + } + isBuffering = buffering; + if (buffering) { + events.onBufferingStart(); + } else { + events.onBufferingEnd(); + } + } + + @SuppressWarnings("SuspiciousNameCombination") + private void sendInitialized() { + if (isInitialized) { + return; + } + isInitialized = true; + VideoSize videoSize = exoPlayer.getVideoSize(); + int rotationCorrection = 0; + int width = videoSize.width; + int height = videoSize.height; + if (width != 0 && height != 0) { + int rotationDegrees = videoSize.unappliedRotationDegrees; + // Switch the width/height if video was taken in portrait mode + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = videoSize.height; + height = videoSize.width; + } + // Rotating the video with ExoPlayer does not seem to be possible with a Surface, + // so inform the Flutter code that the widget needs to be rotated to prevent + // upside-down playback for videos with rotationDegrees of 180 (other orientations work + // correctly without correction). + if (rotationDegrees == 180) { + rotationCorrection = rotationDegrees; + } + } + events.onInitialized(width, height, exoPlayer.getDuration(), rotationCorrection); + } + + @Override + public void onPlaybackStateChanged(final int playbackState) { + switch (playbackState) { + case Player.STATE_BUFFERING: + setBuffering(true); + events.onBufferingUpdate(exoPlayer.getBufferedPosition()); + break; + case Player.STATE_READY: + sendInitialized(); + break; + case Player.STATE_ENDED: + events.onCompleted(); + break; + case Player.STATE_IDLE: + break; + } + if (playbackState != Player.STATE_BUFFERING) { + setBuffering(false); + } + } + + @Override + public void onPlayerError(@NonNull final PlaybackException error) { + setBuffering(false); + if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { + // See https://exoplayer.dev/live-streaming.html#behindlivewindowexception-and-error_code_behind_live_window + exoPlayer.seekToDefaultPosition(); + exoPlayer.prepare(); + } else { + events.onError("VideoError", "Video player had error " + error, null); + } + } + + @Override + public void onIsPlayingChanged(boolean isPlaying) { + events.onIsPlayingStateUpdate(isPlaying); + } +} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/LocalVideoAsset.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/LocalVideoAsset.java new file mode 100644 index 00000000000..3d1b3d850d2 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/LocalVideoAsset.java @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.media3.common.MediaItem; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.exoplayer.source.MediaSource; + +final class LocalVideoAsset extends VideoAsset { + LocalVideoAsset(@NonNull String assetUrl) { + super(assetUrl); + } + + @NonNull + @Override + MediaItem getMediaItem() { + return new MediaItem.Builder().setUri(assetUrl).build(); + } + + @Override + MediaSource.Factory getMediaSourceFactory(Context context) { + return new DefaultMediaSourceFactory(context); + } +} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/RemoteVideoAsset.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/RemoteVideoAsset.java new file mode 100644 index 00000000000..75c3c42d96f --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/RemoteVideoAsset.java @@ -0,0 +1,97 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.OptIn; +import androidx.annotation.VisibleForTesting; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MimeTypes; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.datasource.DefaultHttpDataSource; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.exoplayer.source.MediaSource; +import java.util.Map; + +final class RemoteVideoAsset extends VideoAsset { + private static final String DEFAULT_USER_AGENT = "ExoPlayer"; + private static final String HEADER_USER_AGENT = "User-Agent"; + + @NonNull private final StreamingFormat streamingFormat; + @NonNull private final Map httpHeaders; + + RemoteVideoAsset( + @Nullable String assetUrl, + @NonNull StreamingFormat streamingFormat, + @NonNull Map httpHeaders) { + super(assetUrl); + this.streamingFormat = streamingFormat; + this.httpHeaders = httpHeaders; + } + + @NonNull + @Override + MediaItem getMediaItem() { + MediaItem.Builder builder = new MediaItem.Builder().setUri(assetUrl); + String mimeType = null; + switch (streamingFormat) { + case SMOOTH: + mimeType = MimeTypes.APPLICATION_SS; + break; + case DYNAMIC_ADAPTIVE: + mimeType = MimeTypes.APPLICATION_MPD; + break; + case HTTP_LIVE: + mimeType = MimeTypes.APPLICATION_M3U8; + break; + } + if (mimeType != null) { + builder.setMimeType(mimeType); + } + return builder.build(); + } + + @Override + MediaSource.Factory getMediaSourceFactory(Context context) { + return getMediaSourceFactory(context, new DefaultHttpDataSource.Factory()); + } + + /** + * Returns a configured media source factory, starting at the provided factory. + * + *

This method is provided for ease of testing without making real HTTP calls. + * + * @param context application context. + * @param initialFactory initial factory, to be configured. + * @return configured factory, or {@code null} if not needed for this asset type. + */ + @VisibleForTesting + MediaSource.Factory getMediaSourceFactory( + Context context, DefaultHttpDataSource.Factory initialFactory) { + String userAgent = DEFAULT_USER_AGENT; + if (!httpHeaders.isEmpty() && httpHeaders.containsKey(HEADER_USER_AGENT)) { + userAgent = httpHeaders.get(HEADER_USER_AGENT); + } + unstableUpdateDataSourceFactory(initialFactory, httpHeaders, userAgent); + DataSource.Factory dataSoruceFactory = new DefaultDataSource.Factory(context, initialFactory); + return new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSoruceFactory); + } + + // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. + @OptIn(markerClass = UnstableApi.class) + private static void unstableUpdateDataSourceFactory( + @NonNull DefaultHttpDataSource.Factory factory, + @NonNull Map httpHeaders, + @Nullable String userAgent) { + factory.setUserAgent(userAgent).setAllowCrossProtocolRedirects(true); + if (!httpHeaders.isEmpty()) { + factory.setDefaultRequestProperties(httpHeaders); + } + } +} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAsset.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAsset.java new file mode 100644 index 00000000000..2b83437c6fc --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAsset.java @@ -0,0 +1,83 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media3.common.MediaItem; +import androidx.media3.exoplayer.source.MediaSource; +import java.util.HashMap; +import java.util.Map; + +/** A video to be played by {@link VideoPlayer}. */ +abstract class VideoAsset { + /** + * Returns an asset from a local {@code asset:///} URL, i.e. an on-device asset. + * + * @param assetUrl local asset, beginning in {@code asset:///}. + * @return the asset. + */ + @NonNull + static VideoAsset fromAssetUrl(@NonNull String assetUrl) { + if (!assetUrl.startsWith("asset:///")) { + throw new IllegalArgumentException("assetUrl must start with 'asset:///'"); + } + return new LocalVideoAsset(assetUrl); + } + + /** + * Returns an asset from a remote URL. + * + * @param remoteUrl remote asset, i.e. typically beginning with {@code https://} or similar. + * @param streamingFormat which streaming format, provided as a hint if able. + * @param httpHeaders HTTP headers to set for a request. + * @return the asset. + */ + @NonNull + static VideoAsset fromRemoteUrl( + @Nullable String remoteUrl, + @NonNull StreamingFormat streamingFormat, + @NonNull Map httpHeaders) { + return new RemoteVideoAsset(remoteUrl, streamingFormat, new HashMap<>(httpHeaders)); + } + + @Nullable protected final String assetUrl; + + protected VideoAsset(@Nullable String assetUrl) { + this.assetUrl = assetUrl; + } + + /** + * Returns the configured media item to be played. + * + * @return media item. + */ + @NonNull + abstract MediaItem getMediaItem(); + + /** + * Returns the configured media source factory, if needed for this asset type. + * + * @param context application context. + * @return configured factory, or {@code null} if not needed for this asset type. + */ + abstract MediaSource.Factory getMediaSourceFactory(Context context); + + /** Streaming formats that can be provided to the video player as a hint. */ + enum StreamingFormat { + /** Default, if the format is either not known or not another valid format. */ + UNKNOWN, + + /** Smooth Streaming. */ + SMOOTH, + + /** MPEG-DASH (Dynamic Adaptive over HTTP). */ + DYNAMIC_ADAPTIVE, + + /** HTTP Live Streaming (HLS). */ + HTTP_LIVE + } +} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index b8b78191230..ab20b30f42d 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -4,257 +4,78 @@ package io.flutter.plugins.videoplayer; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; +import static androidx.media3.common.Player.REPEAT_MODE_ALL; +import static androidx.media3.common.Player.REPEAT_MODE_OFF; import android.content.Context; -import android.net.Uri; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Player.Listener; -import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; -import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSource; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; -import com.google.android.exoplayer2.util.Util; -import io.flutter.plugin.common.EventChannel; +import androidx.media3.common.AudioAttributes; +import androidx.media3.common.C; +import androidx.media3.common.MediaItem; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.exoplayer.ExoPlayer; import io.flutter.view.TextureRegistry; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; final class VideoPlayer { - private static final String FORMAT_SS = "ss"; - private static final String FORMAT_DASH = "dash"; - private static final String FORMAT_HLS = "hls"; - private static final String FORMAT_OTHER = "other"; - private ExoPlayer exoPlayer; - private Surface surface; - private final TextureRegistry.SurfaceTextureEntry textureEntry; - - private QueuingEventSink eventSink; - - private final EventChannel eventChannel; - - private static final String USER_AGENT = "User-Agent"; - - @VisibleForTesting boolean isInitialized = false; - + private final VideoPlayerCallbacks videoPlayerEvents; private final VideoPlayerOptions options; - private DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory(); - - VideoPlayer( + /** + * Creates a video player. + * + * @param context application context. + * @param events event callbacks. + * @param textureEntry texture to render to. + * @param asset asset to play. + * @param options options for playback. + * @return a video player instance. + */ + @NonNull + static VideoPlayer create( Context context, - EventChannel eventChannel, + VideoPlayerCallbacks events, TextureRegistry.SurfaceTextureEntry textureEntry, - String dataSource, - String formatHint, - @NonNull Map httpHeaders, + VideoAsset asset, VideoPlayerOptions options) { - this.eventChannel = eventChannel; - this.textureEntry = textureEntry; - this.options = options; - - ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build(); - Uri uri = Uri.parse(dataSource); - - buildHttpDataSourceFactory(httpHeaders); - DataSource.Factory dataSourceFactory = - new DefaultDataSource.Factory(context, httpDataSourceFactory); - - MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint); - - exoPlayer.setMediaSource(mediaSource); - exoPlayer.prepare(); - - setUpVideoPlayer(exoPlayer, new QueuingEventSink()); + ExoPlayer.Builder builder = + new ExoPlayer.Builder(context).setMediaSourceFactory(asset.getMediaSourceFactory(context)); + return new VideoPlayer(builder, events, textureEntry, asset.getMediaItem(), options); } - // Constructor used to directly test members of this class. @VisibleForTesting VideoPlayer( - ExoPlayer exoPlayer, - EventChannel eventChannel, + ExoPlayer.Builder builder, + VideoPlayerCallbacks events, TextureRegistry.SurfaceTextureEntry textureEntry, - VideoPlayerOptions options, - QueuingEventSink eventSink, - DefaultHttpDataSource.Factory httpDataSourceFactory) { - this.eventChannel = eventChannel; + MediaItem mediaItem, + VideoPlayerOptions options) { + this.videoPlayerEvents = events; this.textureEntry = textureEntry; this.options = options; - this.httpDataSourceFactory = httpDataSourceFactory; - - setUpVideoPlayer(exoPlayer, eventSink); - } - - @VisibleForTesting - public void buildHttpDataSourceFactory(@NonNull Map httpHeaders) { - final boolean httpHeadersNotEmpty = !httpHeaders.isEmpty(); - final String userAgent = - httpHeadersNotEmpty && httpHeaders.containsKey(USER_AGENT) - ? httpHeaders.get(USER_AGENT) - : "ExoPlayer"; - httpDataSourceFactory.setUserAgent(userAgent).setAllowCrossProtocolRedirects(true); - - if (httpHeadersNotEmpty) { - httpDataSourceFactory.setDefaultRequestProperties(httpHeaders); - } - } + ExoPlayer exoPlayer = builder.build(); + exoPlayer.setMediaItem(mediaItem); + exoPlayer.prepare(); - private MediaSource buildMediaSource( - Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint) { - int type; - if (formatHint == null) { - type = Util.inferContentType(uri); - } else { - switch (formatHint) { - case FORMAT_SS: - type = C.CONTENT_TYPE_SS; - break; - case FORMAT_DASH: - type = C.CONTENT_TYPE_DASH; - break; - case FORMAT_HLS: - type = C.CONTENT_TYPE_HLS; - break; - case FORMAT_OTHER: - type = C.CONTENT_TYPE_OTHER; - break; - default: - type = -1; - break; - } - } - switch (type) { - case C.CONTENT_TYPE_SS: - return new SsMediaSource.Factory( - new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mediaDataSourceFactory) - .createMediaSource(MediaItem.fromUri(uri)); - case C.CONTENT_TYPE_DASH: - return new DashMediaSource.Factory( - new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mediaDataSourceFactory) - .createMediaSource(MediaItem.fromUri(uri)); - case C.CONTENT_TYPE_HLS: - return new HlsMediaSource.Factory(mediaDataSourceFactory) - .createMediaSource(MediaItem.fromUri(uri)); - case C.CONTENT_TYPE_OTHER: - return new ProgressiveMediaSource.Factory(mediaDataSourceFactory) - .createMediaSource(MediaItem.fromUri(uri)); - default: - { - throw new IllegalStateException("Unsupported type: " + type); - } - } + setUpVideoPlayer(exoPlayer); } - private void setUpVideoPlayer(ExoPlayer exoPlayer, QueuingEventSink eventSink) { + private void setUpVideoPlayer(ExoPlayer exoPlayer) { this.exoPlayer = exoPlayer; - this.eventSink = eventSink; - - eventChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink sink) { - eventSink.setDelegate(sink); - } - - @Override - public void onCancel(Object o) { - eventSink.setDelegate(null); - } - }); surface = new Surface(textureEntry.surfaceTexture()); exoPlayer.setVideoSurface(surface); setAudioAttributes(exoPlayer, options.mixWithOthers); - - exoPlayer.addListener( - new Listener() { - private boolean isBuffering = false; - - public void setBuffering(boolean buffering) { - if (isBuffering != buffering) { - isBuffering = buffering; - Map event = new HashMap<>(); - event.put("event", isBuffering ? "bufferingStart" : "bufferingEnd"); - eventSink.success(event); - } - } - - @Override - public void onPlaybackStateChanged(final int playbackState) { - if (playbackState == Player.STATE_BUFFERING) { - setBuffering(true); - sendBufferingUpdate(); - } else if (playbackState == Player.STATE_READY) { - if (!isInitialized) { - isInitialized = true; - sendInitialized(); - } - } else if (playbackState == Player.STATE_ENDED) { - Map event = new HashMap<>(); - event.put("event", "completed"); - eventSink.success(event); - } - - if (playbackState != Player.STATE_BUFFERING) { - setBuffering(false); - } - } - - @Override - public void onPlayerError(@NonNull final PlaybackException error) { - setBuffering(false); - if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { - // See https://exoplayer.dev/live-streaming.html#behindlivewindowexception-and-error_code_behind_live_window - exoPlayer.seekToDefaultPosition(); - exoPlayer.prepare(); - } else if (eventSink != null) { - eventSink.error("VideoError", "Video player had error " + error, null); - } - } - - @Override - public void onIsPlayingChanged(boolean isPlaying) { - if (eventSink != null) { - Map event = new HashMap<>(); - event.put("event", "isPlayingStateUpdate"); - event.put("isPlaying", isPlaying); - eventSink.success(event); - } - } - }); + exoPlayer.addListener(new ExoPlayerEventListener(exoPlayer, videoPlayerEvents)); } void sendBufferingUpdate() { - Map event = new HashMap<>(); - event.put("event", "bufferingUpdate"); - List range = Arrays.asList(0, exoPlayer.getBufferedPosition()); - // iOS supports a list of buffered ranges, so here is a list with a single range. - event.put("values", Collections.singletonList(range)); - eventSink.success(event); + videoPlayerEvents.onBufferingUpdate(exoPlayer.getBufferedPosition()); } private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) { @@ -296,46 +117,8 @@ long getPosition() { return exoPlayer.getCurrentPosition(); } - @SuppressWarnings("SuspiciousNameCombination") - @VisibleForTesting - void sendInitialized() { - if (isInitialized) { - Map event = new HashMap<>(); - event.put("event", "initialized"); - event.put("duration", exoPlayer.getDuration()); - - if (exoPlayer.getVideoFormat() != null) { - Format videoFormat = exoPlayer.getVideoFormat(); - int width = videoFormat.width; - int height = videoFormat.height; - int rotationDegrees = videoFormat.rotationDegrees; - // Switch the width/height if video was taken in portrait mode - if (rotationDegrees == 90 || rotationDegrees == 270) { - width = exoPlayer.getVideoFormat().height; - height = exoPlayer.getVideoFormat().width; - } - event.put("width", width); - event.put("height", height); - - // Rotating the video with ExoPlayer does not seem to be possible with a Surface, - // so inform the Flutter code that the widget needs to be rotated to prevent - // upside-down playback for videos with rotationDegrees of 180 (other orientations work - // correctly without correction). - if (rotationDegrees == 180) { - event.put("rotationCorrection", rotationDegrees); - } - } - - eventSink.success(event); - } - } - void dispose() { - if (isInitialized) { - exoPlayer.stop(); - } textureEntry.release(); - eventChannel.setStreamHandler(null); if (surface != null) { surface.release(); } diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerCallbacks.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerCallbacks.java new file mode 100644 index 00000000000..b3a1a3967d8 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerCallbacks.java @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Callbacks representing events invoked by {@link VideoPlayer}. + * + *

In the actual plugin, this will always be {@link VideoPlayerEventCallbacks}, which creates the + * expected events to send back through the plugin channel. In tests methods can be overridden in + * order to assert results. + * + *

See {@link androidx.media3.common.Player.Listener} for details. + */ +interface VideoPlayerCallbacks { + void onInitialized(int width, int height, long durationInMs, int rotationCorrectionInDegrees); + + void onBufferingStart(); + + void onBufferingUpdate(long bufferedPosition); + + void onBufferingEnd(); + + void onCompleted(); + + void onError(@NonNull String code, @Nullable String message, @Nullable Object details); + + void onIsPlayingStateUpdate(boolean isPlaying); +} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacks.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacks.java new file mode 100644 index 00000000000..8b83aded168 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacks.java @@ -0,0 +1,104 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import io.flutter.plugin.common.EventChannel; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final class VideoPlayerEventCallbacks implements VideoPlayerCallbacks { + private final EventChannel.EventSink eventSink; + + static VideoPlayerEventCallbacks bindTo(EventChannel eventChannel) { + QueuingEventSink eventSink = new QueuingEventSink(); + eventChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object arguments, EventChannel.EventSink events) { + eventSink.setDelegate(events); + } + + @Override + public void onCancel(Object arguments) { + eventSink.setDelegate(null); + } + }); + return VideoPlayerEventCallbacks.withSink(eventSink); + } + + @VisibleForTesting + static VideoPlayerEventCallbacks withSink(EventChannel.EventSink eventSink) { + return new VideoPlayerEventCallbacks(eventSink); + } + + private VideoPlayerEventCallbacks(EventChannel.EventSink eventSink) { + this.eventSink = eventSink; + } + + @Override + public void onInitialized( + int width, int height, long durationInMs, int rotationCorrectionInDegrees) { + Map event = new HashMap<>(); + event.put("event", "initialized"); + event.put("width", width); + event.put("height", height); + event.put("duration", durationInMs); + if (rotationCorrectionInDegrees != 0) { + event.put("rotationCorrection", rotationCorrectionInDegrees); + } + eventSink.success(event); + } + + @Override + public void onBufferingStart() { + Map event = new HashMap<>(); + event.put("event", "bufferingStart"); + eventSink.success(event); + } + + @Override + public void onBufferingUpdate(long bufferedPosition) { + // iOS supports a list of buffered ranges, so we send as a list with a single range. + Map event = new HashMap<>(); + event.put("event", "bufferingUpdate"); + + List range = Arrays.asList(0, bufferedPosition); + event.put("values", Collections.singletonList(range)); + eventSink.success(event); + } + + @Override + public void onBufferingEnd() { + Map event = new HashMap<>(); + event.put("event", "bufferingEnd"); + eventSink.success(event); + } + + @Override + public void onCompleted() { + Map event = new HashMap<>(); + event.put("event", "completed"); + eventSink.success(event); + } + + @Override + public void onError(@NonNull String code, @Nullable String message, @Nullable Object details) { + eventSink.error(code, message, details); + } + + @Override + public void onIsPlayingStateUpdate(boolean isPlaying) { + Map event = new HashMap<>(); + event.put("event", "isPlayingStateUpdate"); + event.put("isPlaying", isPlaying); + eventSink.success(event); + } +} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index aa9b5bdaa1b..0e57068944e 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -24,7 +24,6 @@ import io.flutter.view.TextureRegistry; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; import java.util.Map; import javax.net.ssl.HttpsURLConnection; @@ -38,30 +37,6 @@ public class VideoPlayerPlugin implements FlutterPlugin, AndroidVideoPlayerApi { /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */ public VideoPlayerPlugin() {} - @SuppressWarnings("deprecation") - private VideoPlayerPlugin(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - this.flutterState = - new FlutterState( - registrar.context(), - registrar.messenger(), - registrar::lookupKeyForAsset, - registrar::lookupKeyForAsset, - registrar.textures()); - flutterState.startListening(this, registrar.messenger()); - } - - /** Registers this with the stable v1 embedding. Will not respond to lifecycle events. */ - @SuppressWarnings("deprecation") - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - final VideoPlayerPlugin plugin = new VideoPlayerPlugin(registrar); - registrar.addViewDestroyListener( - view -> { - plugin.onDestroy(); - return false; // We are not interested in assuming ownership of the NativeView. - }); - } - @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { @@ -125,7 +100,7 @@ public void initialize() { new EventChannel( flutterState.binaryMessenger, "flutter.io/videoPlayer/videoEvents" + handle.id()); - VideoPlayer player; + final VideoAsset videoAsset; if (arg.getAsset() != null) { String assetLookupKey; if (arg.getPackageName() != null) { @@ -134,28 +109,34 @@ public void initialize() { } else { assetLookupKey = flutterState.keyForAsset.get(arg.getAsset()); } - player = - new VideoPlayer( - flutterState.applicationContext, - eventChannel, - handle, - "asset:///" + assetLookupKey, - null, - new HashMap<>(), - options); + videoAsset = VideoAsset.fromAssetUrl("asset:///" + assetLookupKey); } else { Map httpHeaders = arg.getHttpHeaders(); - player = - new VideoPlayer( - flutterState.applicationContext, - eventChannel, - handle, - arg.getUri(), - arg.getFormatHint(), - httpHeaders, - options); + VideoAsset.StreamingFormat streamingFormat = VideoAsset.StreamingFormat.UNKNOWN; + String formatHint = arg.getFormatHint(); + if (formatHint != null) { + switch (formatHint) { + case "ss": + streamingFormat = VideoAsset.StreamingFormat.SMOOTH; + break; + case "dash": + streamingFormat = VideoAsset.StreamingFormat.DYNAMIC_ADAPTIVE; + break; + case "hls": + streamingFormat = VideoAsset.StreamingFormat.HTTP_LIVE; + break; + } + } + videoAsset = VideoAsset.fromRemoteUrl(arg.getUri(), streamingFormat, arg.getHttpHeaders()); } - videoPlayers.put(handle.id(), player); + videoPlayers.put( + handle.id(), + VideoPlayer.create( + flutterState.applicationContext, + VideoPlayerEventCallbacks.bindTo(eventChannel), + handle, + videoAsset, + options)); return new TextureMessage.Builder().setTextureId(handle.id()).build(); } diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/ExoPlayerEventListenerTests.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/ExoPlayerEventListenerTests.java new file mode 100644 index 00000000000..1d00d31b8ee --- /dev/null +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/ExoPlayerEventListenerTests.java @@ -0,0 +1,175 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import androidx.media3.common.PlaybackException; +import androidx.media3.common.Player; +import androidx.media3.common.VideoSize; +import androidx.media3.exoplayer.ExoPlayer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** + * Unit tests for {@link ExoPlayerEventListener}. + * + *

This test suite narrowly verifies that the events emitted by the underlying {@link + * androidx.media3.exoplayer.ExoPlayer} instance are translated to the callback interface we expect + * ({@link VideoPlayerCallbacks} and/or interface with the player instance as expected. + */ +@RunWith(RobolectricTestRunner.class) +public final class ExoPlayerEventListenerTests { + @Mock private ExoPlayer mockExoPlayer; + @Mock private VideoPlayerCallbacks mockCallbacks; + private ExoPlayerEventListener eventListener; + + @Rule public MockitoRule initRule = MockitoJUnit.rule(); + + @Before + public void setUp() { + eventListener = new ExoPlayerEventListener(mockExoPlayer, mockCallbacks); + } + + @Test + public void onPlaybackStateChangedReadySendInitialized() { + VideoSize size = new VideoSize(800, 400, 0, 0); + when(mockExoPlayer.getVideoSize()).thenReturn(size); + when(mockExoPlayer.getDuration()).thenReturn(10L); + + eventListener.onPlaybackStateChanged(Player.STATE_READY); + verify(mockCallbacks).onInitialized(800, 400, 10L, 0); + } + + @Test + public void onPlaybackStateChangedReadyInPortraitMode90DegreesSwapWidthAndHeight() { + VideoSize size = new VideoSize(800, 400, 90, 0); + when(mockExoPlayer.getVideoSize()).thenReturn(size); + when(mockExoPlayer.getDuration()).thenReturn(10L); + + eventListener.onPlaybackStateChanged(Player.STATE_READY); + verify(mockCallbacks).onInitialized(400, 800, 10L, 0); + } + + @Test + public void onPlaybackStateChangedReadyInPortraitMode270DegreesSwapWidthAndHeight() { + VideoSize size = new VideoSize(800, 400, 270, 0); + when(mockExoPlayer.getVideoSize()).thenReturn(size); + when(mockExoPlayer.getDuration()).thenReturn(10L); + + eventListener.onPlaybackStateChanged(Player.STATE_READY); + verify(mockCallbacks).onInitialized(400, 800, 10L, 0); + } + + @Test + public void onPlaybackStateChangedReadyFlipped180DegreesInformEventHandler() { + VideoSize size = new VideoSize(800, 400, 180, 0); + when(mockExoPlayer.getVideoSize()).thenReturn(size); + when(mockExoPlayer.getDuration()).thenReturn(10L); + + eventListener.onPlaybackStateChanged(Player.STATE_READY); + verify(mockCallbacks).onInitialized(800, 400, 10L, 180); + } + + @Test + public void onPlaybackStateChangedBufferingSendsBufferingStartAndUpdates() { + when(mockExoPlayer.getBufferedPosition()).thenReturn(10L); + eventListener.onPlaybackStateChanged(Player.STATE_BUFFERING); + + verify(mockCallbacks).onBufferingStart(); + verify(mockCallbacks).onBufferingUpdate(10L); + verifyNoMoreInteractions(mockCallbacks); + + // If it's invoked again, only the update event is called. + verify(mockCallbacks).onBufferingUpdate(10L); + verifyNoMoreInteractions(mockCallbacks); + } + + @Test + public void onPlaybackStateChangedEndedSendsOnCompleted() { + eventListener.onPlaybackStateChanged(Player.STATE_ENDED); + + verify(mockCallbacks).onCompleted(); + verifyNoMoreInteractions(mockCallbacks); + } + + @Test + public void onPlaybackStateChangedEndedAfterBufferingSendsBufferingEndAndOnCompleted() { + when(mockExoPlayer.getBufferedPosition()).thenReturn(10L); + eventListener.onPlaybackStateChanged(Player.STATE_BUFFERING); + verify(mockCallbacks).onBufferingStart(); + verify(mockCallbacks).onBufferingUpdate(10L); + + eventListener.onPlaybackStateChanged(Player.STATE_ENDED); + verify(mockCallbacks).onCompleted(); + verify(mockCallbacks).onBufferingEnd(); + + verifyNoMoreInteractions(mockCallbacks); + } + + @Test + public void onPlaybackStateChangedIdleDoNothing() { + eventListener.onPlaybackStateChanged(Player.STATE_IDLE); + + verifyNoInteractions(mockCallbacks); + } + + @Test + public void onPlaybackStateChangedIdleAfterBufferingSendsBufferingEnd() { + when(mockExoPlayer.getBufferedPosition()).thenReturn(10L); + eventListener.onPlaybackStateChanged(Player.STATE_BUFFERING); + verify(mockCallbacks).onBufferingStart(); + verify(mockCallbacks).onBufferingUpdate(10L); + + eventListener.onPlaybackStateChanged(Player.STATE_IDLE); + verify(mockCallbacks).onBufferingEnd(); + + verifyNoMoreInteractions(mockCallbacks); + } + + @Test + public void onErrorVideoErrorWhenBufferingInProgressAlsoEndBuffering() { + when(mockExoPlayer.getBufferedPosition()).thenReturn(10L); + eventListener.onPlaybackStateChanged(Player.STATE_BUFFERING); + verify(mockCallbacks).onBufferingStart(); + verify(mockCallbacks).onBufferingUpdate(10L); + + eventListener.onPlayerError( + new PlaybackException("BAD", null, PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED)); + verify(mockCallbacks).onBufferingEnd(); + verify(mockCallbacks).onError(eq("VideoError"), contains("BAD"), isNull()); + } + + @Test + public void onErrorBehindLiveWindowSeekToDefaultAndPrepare() { + eventListener.onPlayerError( + new PlaybackException("SORT_OF_OK", null, PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW)); + + verify(mockExoPlayer).seekToDefaultPosition(); + verify(mockExoPlayer).prepare(); + verifyNoInteractions(mockCallbacks); + } + + @Test + public void onIsPlayingChangedToggled() { + eventListener.onIsPlayingChanged(true); + verify(mockCallbacks).onIsPlayingStateUpdate(true); + + eventListener.onIsPlayingChanged(false); + verify(mockCallbacks).onIsPlayingStateUpdate(false); + } +} diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/FakeVideoAsset.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/FakeVideoAsset.java new file mode 100644 index 00000000000..1e3b856a648 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/FakeVideoAsset.java @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.media3.common.MediaItem; +import androidx.media3.exoplayer.source.MediaSource; +import androidx.media3.test.utils.FakeMediaSourceFactory; + +/** A fake implementation of the {@link VideoAsset} class. */ +final class FakeVideoAsset extends VideoAsset { + @NonNull private final MediaSource.Factory mediaSourceFactory; + + FakeVideoAsset(String assetUrl) { + this(assetUrl, new FakeMediaSourceFactory()); + } + + FakeVideoAsset(String assetUrl, @NonNull MediaSource.Factory mediaSourceFactory) { + super(assetUrl); + this.mediaSourceFactory = mediaSourceFactory; + } + + @NonNull + @Override + MediaItem getMediaItem() { + return new MediaItem.Builder().setUri(assetUrl).build(); + } + + @Override + MediaSource.Factory getMediaSourceFactory(Context context) { + return mediaSourceFactory; + } +} diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoAssetTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoAssetTest.java new file mode 100644 index 00000000000..743bf572aee --- /dev/null +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoAssetTest.java @@ -0,0 +1,138 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.Uri; +import androidx.media3.common.MediaItem; +import androidx.media3.datasource.DefaultHttpDataSource; +import androidx.media3.exoplayer.source.MediaSource; +import androidx.test.core.app.ApplicationProvider; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Unit tests for {@link VideoAsset}. + * + *

This test suite narrowly verifies that the {@link VideoAsset} factory methods, {@link + * VideoAsset#fromRemoteUrl(String, VideoAsset.StreamingFormat, Map)} and {@link + * VideoAsset#fromAssetUrl(String)} follow the contract they have documented. + * + *

In other tests of the player, a fake asset is likely to be used. + */ +@RunWith(RobolectricTestRunner.class) +public final class VideoAssetTest { + @Test + public void localVideoRequiresAssetUrl() { + assertThrows( + IllegalArgumentException.class, + () -> VideoAsset.fromAssetUrl("https://not.local/video.mp4")); + } + + @Test + public void localVideoCreatesMediaItem() { + VideoAsset asset = VideoAsset.fromAssetUrl("asset:///asset-key"); + MediaItem mediaItem = asset.getMediaItem(); + + assert mediaItem.localConfiguration != null; + assertEquals(mediaItem.localConfiguration.uri, Uri.parse("asset:///asset-key")); + } + + private static DefaultHttpDataSource.Factory mockHttpFactory() { + DefaultHttpDataSource.Factory httpFactory = mock(DefaultHttpDataSource.Factory.class); + when(httpFactory.setUserAgent(anyString())).thenReturn(httpFactory); + when(httpFactory.setAllowCrossProtocolRedirects(anyBoolean())).thenReturn(httpFactory); + when(httpFactory.setDefaultRequestProperties(anyMap())).thenReturn(httpFactory); + return httpFactory; + } + + @Test + public void remoteVideoByDefaultSetsUserAgentAndCrossProtocolRedirects() { + VideoAsset asset = + VideoAsset.fromRemoteUrl( + "https://flutter.dev/video.mp4", VideoAsset.StreamingFormat.UNKNOWN, new HashMap<>()); + + DefaultHttpDataSource.Factory mockFactory = mockHttpFactory(); + + // Cast to RemoteVideoAsset to call a testing-only method to intercept calls. + ((RemoteVideoAsset) asset) + .getMediaSourceFactory(ApplicationProvider.getApplicationContext(), mockFactory); + + verify(mockFactory).setUserAgent("ExoPlayer"); + verify(mockFactory).setAllowCrossProtocolRedirects(true); + verify(mockFactory, never()).setDefaultRequestProperties(anyMap()); + } + + @Test + public void remoteVideoOverridesUserAgentIfProvided() { + Map headers = new HashMap<>(); + headers.put("User-Agent", "FantasticalVideoBot"); + + VideoAsset asset = + VideoAsset.fromRemoteUrl( + "https://flutter.dev/video.mp4", VideoAsset.StreamingFormat.UNKNOWN, headers); + + DefaultHttpDataSource.Factory mockFactory = mockHttpFactory(); + + // Cast to RemoteVideoAsset to call a testing-only method to intercept calls. + ((RemoteVideoAsset) asset) + .getMediaSourceFactory(ApplicationProvider.getApplicationContext(), mockFactory); + + verify(mockFactory).setUserAgent("FantasticalVideoBot"); + verify(mockFactory).setAllowCrossProtocolRedirects(true); + verify(mockFactory).setDefaultRequestProperties(headers); + } + + // This tests that without using the overrides we get a working, non-mocked object. + // + // I guess you could also start a local HTTP server, and try fetching with it, YMMV. + @Test + public void remoteVideoGetMediaSourceFactoryInProductionReturnsRealMediaSource() { + VideoAsset asset = + VideoAsset.fromRemoteUrl( + "https://flutter.dev/video.mp4", VideoAsset.StreamingFormat.UNKNOWN, new HashMap<>()); + + MediaSource source = + asset + .getMediaSourceFactory(ApplicationProvider.getApplicationContext()) + .createMediaSource(asset.getMediaItem()); + assertEquals( + Uri.parse("https://flutter.dev/video.mp4"), + Objects.requireNonNull(source.getMediaItem().localConfiguration).uri); + } + + @Test + public void remoteVideoSetsAdditionalHttpHeadersIfProvided() { + Map headers = new HashMap<>(); + headers.put("X-Cache-Forever", "true"); + + VideoAsset asset = + VideoAsset.fromRemoteUrl( + "https://flutter.dev/video.mp4", VideoAsset.StreamingFormat.UNKNOWN, headers); + + DefaultHttpDataSource.Factory mockFactory = mockHttpFactory(); + + // Cast to RemoteVideoAsset to call a testing-only method to intercept calls. + ((RemoteVideoAsset) asset) + .getMediaSourceFactory(ApplicationProvider.getApplicationContext(), mockFactory); + + verify(mockFactory).setUserAgent("ExoPlayer"); + verify(mockFactory).setAllowCrossProtocolRedirects(true); + verify(mockFactory).setDefaultRequestProperties(headers); + } +} diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacksTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacksTest.java new file mode 100644 index 00000000000..d955384bc94 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacksTest.java @@ -0,0 +1,151 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** + * Unit tests {@link VideoPlayerEventCallbacks}. + * + *

This test suite narrowly verifies that calling the provided event callbacks, such as + * {@link VideoPlayerEventCallbacks#onBufferingUpdate(long)}, produces the expected data as an + * encoded {@link Map}. + * + *

In other words, this tests that "the Java-side of the event channel works as expected". + */ +@RunWith(RobolectricTestRunner.class) +public final class VideoPlayerEventCallbacksTest { + private VideoPlayerEventCallbacks eventCallbacks; + + @Mock private QueuingEventSink mockEventSink; + + @Captor private ArgumentCaptor> eventCaptor; + + @Rule public MockitoRule initRule = MockitoJUnit.rule(); + + @Before + public void setUp() { + eventCallbacks = VideoPlayerEventCallbacks.withSink(mockEventSink); + } + + @Test + public void onInitializedSendsWidthHeightAndDuration() { + eventCallbacks.onInitialized(800, 400, 10L, 0); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "initialized"); + expected.put("duration", 10L); + expected.put("width", 800); + expected.put("height", 400); + + assertEquals(expected, actual); + } + + @Test + public void onInitializedIncludesRotationCorrectIfNonZero() { + eventCallbacks.onInitialized(800, 400, 10L, 180); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "initialized"); + expected.put("duration", 10L); + expected.put("width", 800); + expected.put("height", 400); + expected.put("rotationCorrection", 180); + + assertEquals(expected, actual); + } + + @Test + public void onBufferingStart() { + eventCallbacks.onBufferingStart(); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "bufferingStart"); + assertEquals(expected, actual); + } + + @Test + public void onBufferingUpdateProvidesAListWithASingleRange() { + eventCallbacks.onBufferingUpdate(10L); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "bufferingUpdate"); + expected.put("values", Collections.singletonList(Arrays.asList(0, 10L))); + assertEquals(expected, actual); + } + + @Test + public void onBufferingEnd() { + eventCallbacks.onBufferingEnd(); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "bufferingEnd"); + assertEquals(expected, actual); + } + + @Test + public void onCompleted() { + eventCallbacks.onCompleted(); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "completed"); + assertEquals(expected, actual); + } + + @Test + public void onError() { + eventCallbacks.onError("code", "message", "details"); + + verify(mockEventSink).error(eq("code"), eq("message"), eq("details")); + } + + @Test + public void onIsPlayingStateUpdate() { + eventCallbacks.onIsPlayingStateUpdate(true); + + verify(mockEventSink).success(eventCaptor.capture()); + + Map actual = eventCaptor.getValue(); + Map expected = new HashMap<>(); + expected.put("event", "isPlayingStateUpdate"); + expected.put("isPlaying", true); + assertEquals(expected, actual); + } +} diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java index 7ff5000d3cf..a30166c0aa5 100644 --- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java @@ -6,306 +6,175 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.*; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import android.graphics.SurfaceTexture; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; -import io.flutter.plugin.common.EventChannel; +import androidx.media3.common.AudioAttributes; +import androidx.media3.common.C; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; +import androidx.media3.exoplayer.ExoPlayer; import io.flutter.view.TextureRegistry; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +/** + * Unit tests for {@link VideoPlayer}. + * + *

This test suite narrowly verifies that {@link VideoPlayer} interfaces with the {@link + * ExoPlayer} interface exactly as it did when the test suite was created. That is, if the + * behavior changes, this test will need to change. However, this suite should catch bugs related to + * "this is a safe refactor with no behavior changes". + * + *

It's hypothetically possible to write better tests using {@link + * androidx.media3.test.utils.FakeMediaSource}, but you really need a PhD in the Android media APIs + * in order to figure out how to set everything up so the player "works". + */ @RunWith(RobolectricTestRunner.class) -public class VideoPlayerTest { - private ExoPlayer fakeExoPlayer; - private EventChannel fakeEventChannel; - private TextureRegistry.SurfaceTextureEntry fakeSurfaceTextureEntry; - private SurfaceTexture fakeSurfaceTexture; - private VideoPlayerOptions fakeVideoPlayerOptions; - private QueuingEventSink fakeEventSink; - private DefaultHttpDataSource.Factory httpDataSourceFactorySpy; +public final class VideoPlayerTest { + private static final String FAKE_ASSET_URL = "https://flutter.dev/movie.mp4"; + private FakeVideoAsset fakeVideoAsset; - @Captor private ArgumentCaptor> eventCaptor; + @Mock private VideoPlayerCallbacks mockEvents; + @Mock private TextureRegistry.SurfaceTextureEntry mockTexture; + @Mock private ExoPlayer.Builder mockBuilder; + @Mock private ExoPlayer mockExoPlayer; + @Captor private ArgumentCaptor attributesCaptor; + + @Rule public MockitoRule initRule = MockitoJUnit.rule(); @Before - public void before() { - MockitoAnnotations.openMocks(this); - - fakeExoPlayer = mock(ExoPlayer.class); - fakeEventChannel = mock(EventChannel.class); - fakeSurfaceTextureEntry = mock(TextureRegistry.SurfaceTextureEntry.class); - fakeSurfaceTexture = mock(SurfaceTexture.class); - when(fakeSurfaceTextureEntry.surfaceTexture()).thenReturn(fakeSurfaceTexture); - fakeVideoPlayerOptions = mock(VideoPlayerOptions.class); - fakeEventSink = mock(QueuingEventSink.class); - httpDataSourceFactorySpy = spy(new DefaultHttpDataSource.Factory()); + public void setUp() { + fakeVideoAsset = new FakeVideoAsset(FAKE_ASSET_URL); + when(mockBuilder.build()).thenReturn(mockExoPlayer); + when(mockTexture.surfaceTexture()).thenReturn(mock(SurfaceTexture.class)); } - @Test - public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - - videoPlayer.buildHttpDataSourceFactory(new HashMap<>()); - - verify(httpDataSourceFactorySpy).setUserAgent("ExoPlayer"); - verify(httpDataSourceFactorySpy).setAllowCrossProtocolRedirects(true); - verify(httpDataSourceFactorySpy, never()).setDefaultRequestProperties(any()); + private VideoPlayer createVideoPlayer() { + return createVideoPlayer(new VideoPlayerOptions()); } - @Test - public void - videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNonNullAndUserAgentSpecified() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - Map httpHeaders = - new HashMap() { - { - put("header", "value"); - put("User-Agent", "userAgent"); - } - }; - - videoPlayer.buildHttpDataSourceFactory(httpHeaders); - - verify(httpDataSourceFactorySpy).setUserAgent("userAgent"); - verify(httpDataSourceFactorySpy).setAllowCrossProtocolRedirects(true); - verify(httpDataSourceFactorySpy).setDefaultRequestProperties(httpHeaders); + private VideoPlayer createVideoPlayer(VideoPlayerOptions options) { + return new VideoPlayer( + mockBuilder, mockEvents, mockTexture, fakeVideoAsset.getMediaItem(), options); } @Test - public void - videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNonNullAndUserAgentNotSpecified() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - Map httpHeaders = - new HashMap() { - { - put("header", "value"); - } - }; - - videoPlayer.buildHttpDataSourceFactory(httpHeaders); - - verify(httpDataSourceFactorySpy).setUserAgent("ExoPlayer"); - verify(httpDataSourceFactorySpy).setAllowCrossProtocolRedirects(true); - verify(httpDataSourceFactorySpy).setDefaultRequestProperties(httpHeaders); + public void loadsAndPreparesProvidedMediaEnablesAudioFocusByDefault() { + VideoPlayer videoPlayer = createVideoPlayer(); + + verify(mockExoPlayer).setMediaItem(fakeVideoAsset.getMediaItem()); + verify(mockExoPlayer).prepare(); + verify(mockTexture).surfaceTexture(); + verify(mockExoPlayer).setVideoSurface(any()); + + verify(mockExoPlayer).setAudioAttributes(attributesCaptor.capture(), eq(true)); + assertEquals(attributesCaptor.getValue().contentType, C.AUDIO_CONTENT_TYPE_MOVIE); + + videoPlayer.dispose(); } @Test - public void sendInitializedSendsExpectedEvent_90RotationDegrees() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - Format testFormat = - new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(90).build(); - - when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); - when(fakeExoPlayer.getDuration()).thenReturn(10L); - - videoPlayer.isInitialized = true; - videoPlayer.sendInitialized(); - - verify(fakeEventSink).success(eventCaptor.capture()); - HashMap event = eventCaptor.getValue(); - - assertEquals(event.get("event"), "initialized"); - assertEquals(event.get("duration"), 10L); - assertEquals(event.get("width"), 200); - assertEquals(event.get("height"), 100); - assertEquals(event.get("rotationCorrection"), null); + public void loadsAndPreparesProvidedMediaDisablesAudioFocusWhenMixModeSet() { + VideoPlayerOptions options = new VideoPlayerOptions(); + options.mixWithOthers = true; + + VideoPlayer videoPlayer = createVideoPlayer(options); + + verify(mockExoPlayer).setAudioAttributes(attributesCaptor.capture(), eq(false)); + assertEquals(attributesCaptor.getValue().contentType, C.AUDIO_CONTENT_TYPE_MOVIE); + + videoPlayer.dispose(); } @Test - public void sendInitializedSendsExpectedEvent_270RotationDegrees() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - Format testFormat = - new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(270).build(); - - when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); - when(fakeExoPlayer.getDuration()).thenReturn(10L); - - videoPlayer.isInitialized = true; - videoPlayer.sendInitialized(); - - verify(fakeEventSink).success(eventCaptor.capture()); - HashMap event = eventCaptor.getValue(); - - assertEquals(event.get("event"), "initialized"); - assertEquals(event.get("duration"), 10L); - assertEquals(event.get("width"), 200); - assertEquals(event.get("height"), 100); - assertEquals(event.get("rotationCorrection"), null); + public void playsAndPausesProvidedMedia() { + VideoPlayer videoPlayer = createVideoPlayer(); + + videoPlayer.play(); + verify(mockExoPlayer).setPlayWhenReady(true); + + videoPlayer.pause(); + verify(mockExoPlayer).setPlayWhenReady(false); + + videoPlayer.dispose(); } @Test - public void sendInitializedSendsExpectedEvent_0RotationDegrees() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - Format testFormat = - new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(0).build(); - - when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); - when(fakeExoPlayer.getDuration()).thenReturn(10L); - - videoPlayer.isInitialized = true; - videoPlayer.sendInitialized(); - - verify(fakeEventSink).success(eventCaptor.capture()); - HashMap event = eventCaptor.getValue(); - - assertEquals(event.get("event"), "initialized"); - assertEquals(event.get("duration"), 10L); - assertEquals(event.get("width"), 100); - assertEquals(event.get("height"), 200); - assertEquals(event.get("rotationCorrection"), null); + public void sendsBufferingUpdatesOnDemand() { + VideoPlayer videoPlayer = createVideoPlayer(); + + when(mockExoPlayer.getBufferedPosition()).thenReturn(10L); + videoPlayer.sendBufferingUpdate(); + verify(mockEvents).onBufferingUpdate(10L); + + videoPlayer.dispose(); } @Test - public void sendInitializedSendsExpectedEvent_180RotationDegrees() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - Format testFormat = - new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(180).build(); - - when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); - when(fakeExoPlayer.getDuration()).thenReturn(10L); - - videoPlayer.isInitialized = true; - videoPlayer.sendInitialized(); - - verify(fakeEventSink).success(eventCaptor.capture()); - HashMap event = eventCaptor.getValue(); - - assertEquals(event.get("event"), "initialized"); - assertEquals(event.get("duration"), 10L); - assertEquals(event.get("width"), 100); - assertEquals(event.get("height"), 200); - assertEquals(event.get("rotationCorrection"), 180); + public void togglesLoopingEnablesAndDisablesRepeatMode() { + VideoPlayer videoPlayer = createVideoPlayer(); + + videoPlayer.setLooping(true); + verify(mockExoPlayer).setRepeatMode(Player.REPEAT_MODE_ALL); + + videoPlayer.setLooping(false); + verify(mockExoPlayer).setRepeatMode(Player.REPEAT_MODE_OFF); + + videoPlayer.dispose(); } @Test - public void onIsPlayingChangedSendsExpectedEvent() { - VideoPlayer videoPlayer = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - - doAnswer( - (Answer) - invocation -> { - Map event = new HashMap<>(); - event.put("event", "isPlayingStateUpdate"); - event.put("isPlaying", (Boolean) invocation.getArguments()[0]); - fakeEventSink.success(event); - return null; - }) - .when(fakeExoPlayer) - .setPlayWhenReady(anyBoolean()); + public void setVolumeIsClampedBetween0and1() { + VideoPlayer videoPlayer = createVideoPlayer(); - videoPlayer.play(); + videoPlayer.setVolume(-1.0); + verify(mockExoPlayer).setVolume(0f); - verify(fakeEventSink).success(eventCaptor.capture()); - HashMap event1 = eventCaptor.getValue(); + videoPlayer.setVolume(2.0); + verify(mockExoPlayer).setVolume(1f); - assertEquals(event1.get("event"), "isPlayingStateUpdate"); - assertEquals(event1.get("isPlaying"), true); + videoPlayer.setVolume(0.5); + verify(mockExoPlayer).setVolume(0.5f); - videoPlayer.pause(); + videoPlayer.dispose(); + } - verify(fakeEventSink, times(2)).success(eventCaptor.capture()); - HashMap event2 = eventCaptor.getValue(); + @Test + public void setPlaybackSpeedSetsPlaybackParametersWithValue() { + VideoPlayer videoPlayer = createVideoPlayer(); - assertEquals(event2.get("event"), "isPlayingStateUpdate"); - assertEquals(event2.get("isPlaying"), false); + videoPlayer.setPlaybackSpeed(2.5); + verify(mockExoPlayer).setPlaybackParameters(new PlaybackParameters(2.5f)); + + videoPlayer.dispose(); } @Test - public void behindLiveWindowErrorResetsPlayerToDefaultPosition() { - List listeners = new LinkedList<>(); - doAnswer(invocation -> listeners.add(invocation.getArgument(0))) - .when(fakeExoPlayer) - .addListener(any()); - - VideoPlayer unused = - new VideoPlayer( - fakeExoPlayer, - fakeEventChannel, - fakeSurfaceTextureEntry, - fakeVideoPlayerOptions, - fakeEventSink, - httpDataSourceFactorySpy); - - PlaybackException exception = - new PlaybackException(null, null, PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW); - listeners.forEach(listener -> listener.onPlayerError(exception)); - - verify(fakeExoPlayer).seekToDefaultPosition(); - verify(fakeExoPlayer).prepare(); + public void seekAndGetPosition() { + VideoPlayer videoPlayer = createVideoPlayer(); + + videoPlayer.seekTo(10); + verify(mockExoPlayer).seekTo(10); + + when(mockExoPlayer.getCurrentPosition()).thenReturn(20L); + assertEquals(20L, videoPlayer.getPosition()); + } + + @Test + public void disposeReleasesTextureAndPlayer() { + VideoPlayer videoPlayer = createVideoPlayer(); + videoPlayer.dispose(); + + verify(mockTexture).release(); + verify(mockExoPlayer).release(); } } diff --git a/packages/video_player/video_player_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/video_player/video_player_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/video_player/video_player_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/video_player/video_player_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/video_player/video_player_android/example/android/build.gradle b/packages/video_player/video_player_android/example/android/build.gradle index 0a2199e8925..d8242047006 100644 --- a/packages/video_player/video_player_android/example/android/build.gradle +++ b/packages/video_player/video_player_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/video_player/video_player_android/example/android/settings.gradle b/packages/video_player/video_player_android/example/android/settings.gradle index 0360c9f26f6..e54a7e1fd6e 100644 --- a/packages/video_player/video_player_android/example/android/settings.gradle +++ b/packages/video_player/video_player_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/video_player/video_player_android/example/pubspec.yaml b/packages/video_player/video_player_android/example/pubspec.yaml index d7eea74093b..a6c57dc8d8b 100644 --- a/packages/video_player/video_player_android/example/pubspec.yaml +++ b/packages/video_player/video_player_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the video_player plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 1ac1fa5ea34..b57f24d100c 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -2,11 +2,11 @@ name: video_player_android description: Android implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.4.14 +version: 2.5.2 environment: - sdk: ^3.2.0 - flutter: ">=3.16.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 80078f24cc4..4ea05f7f1a9 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.6.1 + +* Adds files to make include directory permanent. + +## 2.6.0 + +* Adds Swift Package Manager compatibility. + ## 2.5.7 * Adds frame availability checks on iOS. diff --git a/packages/video_player/video_player_avfoundation/README.md b/packages/video_player/video_player_avfoundation/README.md index 0e20e3a8c3a..1325daf0acf 100644 --- a/packages/video_player/video_player_avfoundation/README.md +++ b/packages/video_player/video_player_avfoundation/README.md @@ -12,4 +12,4 @@ However, if you `import` this package to use any of its APIs directly, you should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/video_player -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation.podspec b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation.podspec index 50b4b2b5baf..81e8d2f8e4e 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation.podspec +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation.podspec @@ -14,14 +14,14 @@ Downloaded by pub (not CocoaPods). s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation' } s.documentation_url = 'https://pub.dev/packages/video_player' - s.source_files = 'Classes/*' - s.ios.source_files = 'Classes/ios/*' - s.osx.source_files = 'Classes/macos/*' - s.public_header_files = 'Classes/**/*.h' + s.source_files = 'video_player_avfoundation/Sources/video_player_avfoundation/**/*.{h,m}' + s.ios.source_files = 'video_player_avfoundation/Sources/video_player_avfoundation_ios/*' + s.osx.source_files = 'video_player_avfoundation/Sources/video_player_avfoundation_macos/*' + s.public_header_files = 'video_player_avfoundation/Sources/video_player_avfoundation/include/**/*.h' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.14' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.resource_bundles = {'video_player_avfoundation_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'video_player_avfoundation_privacy' => ['video_player_avfoundation/Sources/video_player_avfoundation/Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Package.swift b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Package.swift new file mode 100644 index 00000000000..32bd171e33e --- /dev/null +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Package.swift @@ -0,0 +1,46 @@ +// swift-tools-version: 5.9 + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import PackageDescription + +let package = Package( + name: "video_player_avfoundation", + platforms: [ + .iOS("12.0"), + .macOS("10.14"), + ], + products: [ + .library(name: "video-player-avfoundation", targets: ["video_player_avfoundation"]) + ], + dependencies: [], + targets: [ + .target( + name: "video_player_avfoundation", + dependencies: [ + .target(name: "video_player_avfoundation_ios", condition: .when(platforms: [.iOS])), + .target(name: "video_player_avfoundation_macos", condition: .when(platforms: [.macOS])), + ], + resources: [ + .process("Resources") + ], + cSettings: [ + .headerSearchPath("include/video_player_avfoundation") + ] + ), + .target( + name: "video_player_avfoundation_ios", + cSettings: [ + .headerSearchPath("../video_player_avfoundation/include/video_player_avfoundation") + ] + ), + .target( + name: "video_player_avfoundation_macos", + cSettings: [ + .headerSearchPath("../video_player_avfoundation/include/video_player_avfoundation") + ] + ), + ] +) diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/AVAssetTrackUtils.m similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.m rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/AVAssetTrackUtils.m diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m similarity index 99% rename from packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m index 8c76cb15879..14ee7ccefde 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m @@ -8,9 +8,9 @@ #import #import -#import "AVAssetTrackUtils.h" -#import "FVPDisplayLink.h" -#import "messages.g.h" +#import "./include/video_player_avfoundation/AVAssetTrackUtils.h" +#import "./include/video_player_avfoundation/FVPDisplayLink.h" +#import "./include/video_player_avfoundation/messages.g.h" #if !__has_feature(objc_arc) #error Code Requires ARC. diff --git a/packages/video_player/video_player_avfoundation/darwin/Resources/PrivacyInfo.xcprivacy b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Resources/PrivacyInfo.xcprivacy rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/Resources/PrivacyInfo.xcprivacy diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/AVAssetTrackUtils.h similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Classes/AVAssetTrackUtils.h rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/AVAssetTrackUtils.h diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPDisplayLink.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPDisplayLink.h similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Classes/FVPDisplayLink.h rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPDisplayLink.h diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayerPlugin.h similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.h rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayerPlugin.h diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin_Test.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayerPlugin_Test.h similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin_Test.h rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayerPlugin_Test.h diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h similarity index 100% rename from packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.h rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m similarity index 99% rename from packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.m rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m index 97b408b53be..1e2354d8828 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/messages.g.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m @@ -4,7 +4,7 @@ // Autogenerated from Pigeon (v18.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon -#import "messages.g.h" +#import "./include/video_player_avfoundation/messages.g.h" #if TARGET_OS_OSX #import diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPDisplayLink.m similarity index 95% rename from packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPDisplayLink.m index 505001bc223..9bdb321ae16 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/ios/FVPDisplayLink.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPDisplayLink.m @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "../FVPDisplayLink.h" +#import "../video_player_avfoundation/include/video_player_avfoundation/FVPDisplayLink.h" #import #import diff --git a/packages/image_picker/image_picker_ios/ios/Assets/.gitkeep b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/.gitkeep old mode 100755 new mode 100644 similarity index 100% rename from packages/image_picker/image_picker_ios/ios/Assets/.gitkeep rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/.gitkeep diff --git a/packages/dynamic_layouts/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/FVPEmpty.h similarity index 54% rename from packages/dynamic_layouts/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/FVPEmpty.h index b18c9dbb16a..c7b344f7299 100644 --- a/packages/dynamic_layouts/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/FVPEmpty.h @@ -1,8 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package com.example.example -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() {} +// Empty file to perserve include directory in pub-cache. See +// https://github.com/flutter/flutter/issues/148002 for more information. diff --git a/packages/video_player/video_player_avfoundation/darwin/Classes/macos/FVPDisplayLink.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/FVPDisplayLink.m similarity index 97% rename from packages/video_player/video_player_avfoundation/darwin/Classes/macos/FVPDisplayLink.m rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/FVPDisplayLink.m index 3904c8a288a..cd5670fa5a3 100644 --- a/packages/video_player/video_player_avfoundation/darwin/Classes/macos/FVPDisplayLink.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/FVPDisplayLink.m @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "../FVPDisplayLink.h" +#import "../video_player_avfoundation/include/video_player_avfoundation/FVPDisplayLink.h" #import #import diff --git a/packages/ios_platform_images/ios/Assets/.gitkeep b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/.gitkeep similarity index 100% rename from packages/ios_platform_images/ios/Assets/.gitkeep rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/.gitkeep diff --git a/packages/platform/example/android/app/src/main/kotlin/dev/flutter/plaform_example/MainActivity.kt b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/FVPEmpty.h similarity index 54% rename from packages/platform/example/android/app/src/main/kotlin/dev/flutter/plaform_example/MainActivity.kt rename to packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/FVPEmpty.h index 9a1d952b204..c7b344f7299 100644 --- a/packages/platform/example/android/app/src/main/kotlin/dev/flutter/plaform_example/MainActivity.kt +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/FVPEmpty.h @@ -2,8 +2,5 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package dev.flutter.plaform_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() {} +// Empty file to perserve include directory in pub-cache. See +// https://github.com/flutter/flutter/issues/148002 for more information. diff --git a/packages/video_player/video_player_avfoundation/example/ios/Podfile b/packages/video_player/video_player_avfoundation/example/ios/Podfile index 3278285d9d7..c9339a034eb 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Podfile +++ b/packages/video_player/video_player_avfoundation/example/ios/Podfile @@ -31,7 +31,6 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths - pod 'OCMock', '3.9.1' end end diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index feb1ba462cc..0f6dd7a79da 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 78CF8D742BC5CEA80051231B /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = 78CF8D732BC5CEA80051231B /* OCMock */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -99,6 +100,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78CF8D742BC5CEA80051231B /* OCMock in Frameworks */, D182ECB59C06DBC7E2D5D913 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -260,6 +262,9 @@ F7151F4026603ECA0028CB91 /* PBXTargetDependency */, ); name = RunnerTests; + packageProductDependencies = ( + 78CF8D732BC5CEA80051231B /* OCMock */, + ); productName = RunnerTests; productReference = F7151F3A26603ECA0028CB91 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -297,6 +302,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 78CF8D722BC5CEA80051231B /* XCRemoteSwiftPackageReference "ocmock" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -758,6 +766,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 78CF8D722BC5CEA80051231B /* XCRemoteSwiftPackageReference "ocmock" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/erikdoe/ocmock"; + requirement = { + kind = revision; + revision = ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78CF8D732BC5CEA80051231B /* OCMock */ = { + isa = XCSwiftPackageProductDependency; + package = 78CF8D722BC5CEA80051231B /* XCRemoteSwiftPackageReference "ocmock" */; + productName = OCMock; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..69f8d7acc40 --- /dev/null +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,13 @@ +{ + "pins" : [ + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a" + } + } + ], + "version" : 2 +} diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..69f8d7acc40 --- /dev/null +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,13 @@ +{ + "pins" : [ + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a" + } + } + ], + "version" : 2 +} diff --git a/packages/video_player/video_player_avfoundation/example/macos/Podfile b/packages/video_player/video_player_avfoundation/example/macos/Podfile index c0f5d7877b9..c795730db8e 100644 --- a/packages/video_player/video_player_avfoundation/example/macos/Podfile +++ b/packages/video_player/video_player_avfoundation/example/macos/Podfile @@ -33,7 +33,6 @@ target 'Runner' do flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths - pod 'OCMock', '3.9.1' end end diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj index 5159838815f..dc593db1307 100644 --- a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78CF8D772BC5D0140051231B /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = 78CF8D762BC5D0140051231B /* OCMock */; }; C000184E56E3386C22EF683A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC60543320154AF9A465D416 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -95,6 +96,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78CF8D772BC5D0140051231B /* OCMock in Frameworks */, 18AD5E2A5B24DAFCF3749529 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -226,6 +228,9 @@ 331C80DA294CF71000263BE5 /* PBXTargetDependency */, ); name = RunnerTests; + packageProductDependencies = ( + 78CF8D762BC5D0140051231B /* OCMock */, + ); productName = RunnerTests; productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -292,6 +297,9 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 78CF8D752BC5D0140051231B /* XCRemoteSwiftPackageReference "ocmock" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -808,6 +816,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 78CF8D752BC5D0140051231B /* XCRemoteSwiftPackageReference "ocmock" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/erikdoe/ocmock"; + requirement = { + kind = revision; + revision = ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78CF8D762BC5D0140051231B /* OCMock */ = { + isa = XCSwiftPackageProductDependency; + package = 78CF8D752BC5D0140051231B /* XCRemoteSwiftPackageReference "ocmock" */; + productName = OCMock; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..69f8d7acc40 --- /dev/null +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,13 @@ +{ + "pins" : [ + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a" + } + } + ], + "version" : 2 +} diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000000..69f8d7acc40 --- /dev/null +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,13 @@ +{ + "pins" : [ + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "ef21a2ece3ee092f8ed175417718bdd9b8eb7c9a" + } + } + ], + "version" : 2 +} diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner/AppDelegate.swift b/packages/video_player/video_player_avfoundation/example/macos/Runner/AppDelegate.swift index 5cec4c48f62..689c0ecd525 100644 --- a/packages/video_player/video_player_avfoundation/example/macos/Runner/AppDelegate.swift +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner/DebugProfile.entitlements b/packages/video_player/video_player_avfoundation/example/macos/Runner/DebugProfile.entitlements index 3ba6c1266f2..c8f2dc5c736 100644 --- a/packages/video_player/video_player_avfoundation/example/macos/Runner/DebugProfile.entitlements +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.cs.allow-jit com.apple.security.network.client diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner/Release.entitlements b/packages/video_player/video_player_avfoundation/example/macos/Runner/Release.entitlements index ee95ab7e582..5fe13922aa5 100644 --- a/packages/video_player/video_player_avfoundation/example/macos/Runner/Release.entitlements +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner/Release.entitlements @@ -3,7 +3,8 @@ com.apple.security.app-sandbox - + + com.apple.security.network.client diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index 1f9795360c3..8dbda80bd55 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -7,10 +7,13 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartTestOut: 'test/test_api.g.dart', - objcHeaderOut: 'darwin/Classes/messages.g.h', - objcSourceOut: 'darwin/Classes/messages.g.m', + objcHeaderOut: + 'darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h', + objcSourceOut: + 'darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m', objcOptions: ObjcOptions( prefix: 'FVP', + headerIncludePath: './include/video_player_avfoundation/messages.g.h', ), copyrightHeader: 'pigeons/copyright.txt', )) diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 121d3a7a1aa..674684ee789 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.7 +version: 2.6.1 environment: sdk: ^3.2.3 diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 3a7e9573460..ebde5659cf2 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 6.2.2 diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index 4ed09c217bb..acfdcdf68b0 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 6.2.2 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index cac6a397996..d0ef30ca738 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,6 +1,10 @@ +## 2.3.1 + +* Fixes some `package:web` tweaks. + ## 2.3.0 -* Migrates package and tests to `package:web``. +* Migrates package and tests to `package:web`. * Fixes infinite event loop caused by `seekTo` when the video ends. ## 2.2.0 diff --git a/packages/video_player/video_player_web/README.md b/packages/video_player/video_player_web/README.md index 1868a1052d8..82e2c6abc28 100644 --- a/packages/video_player/video_player_web/README.md +++ b/packages/video_player/video_player_web/README.md @@ -4,7 +4,7 @@ The web implementation of [`video_player`][1]. ## Usage -This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), +This package is [endorsed](https://flutter.dev/to/endorsed-federated-plugin), which means you can simply use `video_player` normally. This package will be automatically included in your app when you do, so you do not need to add it to your `pubspec.yaml`. diff --git a/packages/video_player/video_player_web/example/README.md b/packages/video_player/video_player_web/example/README.md index 0e51ae5ecbd..932e9f227cb 100644 --- a/packages/video_player/video_player_web/example/README.md +++ b/packages/video_player/video_player_web/example/README.md @@ -12,8 +12,8 @@ very unlikely to be relevant. This package uses `package:integration_test` to run its tests in a web browser. -See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) -in the Flutter wiki for instructions to setup and run the tests in this package. +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#web-tests) +in the Flutter documentation for instructions to set up and run the tests in this package. -Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +Check [flutter.dev > Integration testing](https://docs.flutter.dev/testing/integration-tests) for more info. diff --git a/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart b/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart index ccf5b32e5c6..f59ba6cbb4a 100644 --- a/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart +++ b/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart @@ -11,12 +11,16 @@ import 'package:web/web.dart' as web; /// Adds a `controlsList` and `disablePictureInPicture` getters. extension NonStandardGettersOnVideoElement on web.HTMLVideoElement { external web.DOMTokenList? get controlsList; - external JSBoolean get disablePictureInPicture; + // TODO(srujzs): This will be added in `package:web` 0.6.0. Remove this helper + // once it's available. + external bool get disablePictureInPicture; } /// Adds a `disableRemotePlayback` getter. extension NonStandardGettersOnMediaElement on web.HTMLMediaElement { - external JSBoolean get disableRemotePlayback; + // TODO(srujzs): This will be added in `package:web` 0.6.0. Remove this helper + // once it's available. + external bool get disableRemotePlayback; } /// Defines JS interop to access static methods from `Object`. diff --git a/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart b/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart index c0ae661c96b..3ba2c94ea92 100644 --- a/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart +++ b/packages/video_player/video_player_web/lib/src/pkg_web_tweaks.dart @@ -2,16 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:js_interop'; import 'package:web/web.dart' as web; /// Adds a "disablePictureInPicture" setter to [web.HTMLVideoElement]s. extension NonStandardSettersOnVideoElement on web.HTMLVideoElement { - external set disablePictureInPicture(JSBoolean disabled); + // TODO(srujzs): This will be added in `package:web` 0.6.0. Remove this helper + // once it's available. + external set disablePictureInPicture(bool disabled); } /// Adds a "disableRemotePlayback" and "controlsList" setters to [web.HTMLMediaElement]s. extension NonStandardSettersOnMediaElement on web.HTMLMediaElement { - external set disableRemotePlayback(JSBoolean disabled); - external set controlsList(JSString? controlsList); + // TODO(srujzs): This will be added in `package:web` 0.6.0. Remove this helper + // once it's available. + external set disableRemotePlayback(bool disabled); + external set controlsList(String? controlsList); } diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index 012463fc780..72f4b7dc155 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 @@ -240,11 +240,11 @@ class VideoPlayer { _videoElement.controls = true; final String controlsList = options.controls.controlsList; if (controlsList.isNotEmpty) { - _videoElement.controlsList = controlsList.toJS; + _videoElement.controlsList = controlsList; } if (!options.controls.allowPictureInPicture) { - _videoElement.disablePictureInPicture = true.toJS; + _videoElement.disablePictureInPicture = true; } } @@ -254,7 +254,7 @@ class VideoPlayer { } if (!options.allowRemotePlayback) { - _videoElement.disableRemotePlayback = true.toJS; + _videoElement.disableRemotePlayback = true; } } diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index dd876328b01..6b1e1887bca 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.3.0 +version: 2.3.1 environment: sdk: ^3.3.0 diff --git a/packages/web_benchmarks/CHANGELOG.md b/packages/web_benchmarks/CHANGELOG.md index ff4ca85cf25..0a5ec61db82 100644 --- a/packages/web_benchmarks/CHANGELOG.md +++ b/packages/web_benchmarks/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.0.0 + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. +* Adds support for running benchmarks with the wasm compilation target. +* **Breaking change** `CompilationOptions` unnamed constructor has been replaced with +two named constructors, `CompilationOptions.js` and `CompilationOptions.wasm` for +JavaScript and WebAssembly compilation respectively. + ## 1.2.2 * Moves flutter_test and test dependencies to dev_dependencies. diff --git a/packages/web_benchmarks/lib/server.dart b/packages/web_benchmarks/lib/server.dart index 1742ac507c9..514b111d95f 100644 --- a/packages/web_benchmarks/lib/server.dart +++ b/packages/web_benchmarks/lib/server.dart @@ -50,7 +50,7 @@ Future serveWebBenchmark({ bool headless = true, bool treeShakeIcons = true, String initialPage = defaultInitialPage, - CompilationOptions compilationOptions = const CompilationOptions(), + CompilationOptions compilationOptions = const CompilationOptions.js(), }) async { // Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy. Logger.root.level = Level.INFO; diff --git a/packages/web_benchmarks/lib/src/compilation_options.dart b/packages/web_benchmarks/lib/src/compilation_options.dart index 3a994703c63..30a349c2520 100644 --- a/packages/web_benchmarks/lib/src/compilation_options.dart +++ b/packages/web_benchmarks/lib/src/compilation_options.dart @@ -7,11 +7,15 @@ /// This object holds metadata that is used to determine how the benchmark app /// should be built. class CompilationOptions { - /// Creates a [CompilationOptions] object. - const CompilationOptions({ + /// Creates a [CompilationOptions] object that compiles to JavaScript. + const CompilationOptions.js({ this.renderer = WebRenderer.canvaskit, - this.useWasm = false, - }); + }) : useWasm = false; + + /// Creates a [CompilationOptions] object that compiles to WebAssembly. + const CompilationOptions.wasm() + : useWasm = true, + renderer = WebRenderer.skwasm; /// The renderer to use for the build. final WebRenderer renderer; diff --git a/packages/web_benchmarks/lib/src/runner.dart b/packages/web_benchmarks/lib/src/runner.dart index 8eeaeb0617a..87e924194e9 100644 --- a/packages/web_benchmarks/lib/src/runner.dart +++ b/packages/web_benchmarks/lib/src/runner.dart @@ -55,7 +55,7 @@ class BenchmarkServer { required this.chromeDebugPort, required this.headless, required this.treeShakeIcons, - this.compilationOptions = const CompilationOptions(), + this.compilationOptions = const CompilationOptions.js(), this.initialPage = defaultInitialPage, }); @@ -119,10 +119,9 @@ class BenchmarkServer { 'web', if (compilationOptions.useWasm) ...[ '--wasm', - '--wasm-opt=debug', - '--omit-type-checks', - ], - '--web-renderer=${compilationOptions.renderer.name}', + '--no-strip-wasm', + ] else + '--web-renderer=${compilationOptions.renderer.name}', '--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true', if (!treeShakeIcons) '--no-tree-shake-icons', '--profile', diff --git a/packages/web_benchmarks/pubspec.yaml b/packages/web_benchmarks/pubspec.yaml index cbf15b25c1d..5b341d3f350 100644 --- a/packages/web_benchmarks/pubspec.yaml +++ b/packages/web_benchmarks/pubspec.yaml @@ -2,7 +2,7 @@ name: web_benchmarks description: A benchmark harness for performance-testing Flutter apps in Chrome. repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22 -version: 1.2.2 +version: 2.0.0 environment: sdk: ^3.3.0 diff --git a/packages/web_benchmarks/testing/test_app/pubspec.yaml b/packages/web_benchmarks/testing/test_app/pubspec.yaml index d7b49213604..0a69219b36b 100644 --- a/packages/web_benchmarks/testing/test_app/pubspec.yaml +++ b/packages/web_benchmarks/testing/test_app/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dependencies: flutter: diff --git a/packages/web_benchmarks/testing/test_app/web/index.html b/packages/web_benchmarks/testing/test_app/web/index.html index 0489be3063c..5b2c184c9a8 100644 --- a/packages/web_benchmarks/testing/test_app/web/index.html +++ b/packages/web_benchmarks/testing/test_app/web/index.html @@ -21,16 +21,6 @@ - - - + diff --git a/packages/web_benchmarks/testing/web_benchmarks_test.dart b/packages/web_benchmarks/testing/web_benchmarks_test.dart index 6c91d4bda4f..547ddee4f7f 100644 --- a/packages/web_benchmarks/testing/web_benchmarks_test.dart +++ b/packages/web_benchmarks/testing/web_benchmarks_test.dart @@ -32,10 +32,9 @@ Future main() async { await _runBenchmarks( benchmarkNames: ['simple'], entryPoint: 'lib/benchmarks/runner_simple.dart', - compilationOptions: const CompilationOptions(useWasm: true), + compilationOptions: const CompilationOptions.wasm(), ); }, - skip: true, // https://github.com/flutter/flutter/issues/142809 timeout: Timeout.none, ); } @@ -44,7 +43,7 @@ Future _runBenchmarks({ required List benchmarkNames, required String entryPoint, String initialPage = defaultInitialPage, - CompilationOptions compilationOptions = const CompilationOptions(), + CompilationOptions compilationOptions = const CompilationOptions.js(), }) async { final BenchmarkResults taskResult = await serveWebBenchmark( benchmarkAppDirectory: Directory('testing/test_app'), diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 48b96405d38..34495364cf5 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.8.0 + +* Adds `onHttpError` callback to `NavigationDelegate` to catch HTTP error status codes. + ## 4.7.0 * Adds support to track scroll position changes. diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index 399b6af4e46..81a33821c06 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -14,7 +14,6 @@ On Android the WebView widget is backed by a [WebView](https://developer.android | **Support** | SDK 19+ or 20+ | 12.0+ | ## Usage -Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://pub.dev/packages/webview_flutter/install). You can now display a WebView by: @@ -32,6 +31,7 @@ controller = WebViewController() }, onPageStarted: (String url) {}, onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { @@ -64,8 +64,8 @@ for more details. ### Android Platform Views This plugin uses -[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed -the Android’s WebView within the Flutter app. +[Platform Views](https://docs.flutter.dev/platform-integration/android/platform-views) to +embed the Android's WebView within the Flutter app. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` if it was previously lower than 19: @@ -142,7 +142,7 @@ for more details on iOS features. ### Enable Material Components for Android To use Material Components when the user interacts with input elements in the WebView, -follow the steps described in the [Enabling Material Components instructions](https://flutter.dev/docs/deployment/android#enabling-material-components). +follow the steps described in the [Enabling Material Components instructions](https://docs.flutter.dev/deployment/android#enable-material-components). ### Setting custom headers on POST requests diff --git a/packages/webview_flutter/webview_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/webview_flutter/webview_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/webview_flutter/webview_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/webview_flutter/webview_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/webview_flutter/webview_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/webview_flutter/webview_flutter/example/android/app/src/main/AndroidManifest.xml index b8c8d38d45a..580051edfeb 100644 --- a/packages/webview_flutter/webview_flutter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/webview_flutter/webview_flutter/example/android/app/src/main/AndroidManifest.xml @@ -15,13 +15,6 @@ android:name="io.flutter.embedding.android.FlutterActivity" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize"> - - diff --git a/packages/webview_flutter/webview_flutter/example/android/build.gradle b/packages/webview_flutter/webview_flutter/example/android/build.gradle index 6ae7ddc2ce5..cec92de922c 100644 --- a/packages/webview_flutter/webview_flutter/example/android/build.gradle +++ b/packages/webview_flutter/webview_flutter/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/webview_flutter/webview_flutter/example/android/settings.gradle b/packages/webview_flutter/webview_flutter/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/webview_flutter/webview_flutter/example/android/settings.gradle +++ b/packages/webview_flutter/webview_flutter/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index a18c1be0345..8460a602ab0 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -66,10 +66,10 @@ Future main() async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setNavigationDelegate( + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), - )); - unawaited(controller.loadRequest(Uri.parse(primaryUrl))); + ); + await controller.loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageFinished.future; @@ -82,11 +82,11 @@ Future main() async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), - )); - unawaited(controller.loadRequest(Uri.parse(primaryUrl))); + ); + await controller.loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -106,14 +106,14 @@ Future main() async { final StreamController pageLoads = StreamController(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (String url) => pageLoads.add(url)), - )); + ); await tester.pumpWidget(WebViewWidget(controller: controller)); - unawaited(controller.loadRequest(Uri.parse(headersUrl), headers: headers)); + await controller.loadRequest(Uri.parse(headersUrl), headers: headers); await pageLoads.stream.firstWhere((String url) => url == headersUrl); @@ -126,10 +126,10 @@ Future main() async { testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), - )); + ); final Completer channelCompleter = Completer(); await controller.addJavaScriptChannel( @@ -187,12 +187,12 @@ Future main() async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageFinished.complete(), - ))); - unawaited(controller.setUserAgent('Custom_User_Agent1')); - unawaited(controller.loadRequest(Uri.parse('about:blank'))); + )); + await controller.setUserAgent('Custom_User_Agent1'); + await controller.loadRequest(Uri.parse('about:blank')); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -256,14 +256,15 @@ Future main() async { WebViewController controller = WebViewController.fromPlatformCreationParams(params); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - )); + ); if (controller.platform is AndroidWebViewController) { - unawaited((controller.platform as AndroidWebViewController) - .setMediaPlaybackRequiresUserGesture(false)); + await (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); } await controller.loadRequest( @@ -272,6 +273,8 @@ Future main() async { await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + await pageLoaded.future; bool isPaused = @@ -280,16 +283,20 @@ Future main() async { pageLoaded = Completer(); controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - )); - unawaited(controller.loadRequest( + ); + + await controller.loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), - )); + ); await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + await pageLoaded.future; isPaused = @@ -312,11 +319,13 @@ Future main() async { } final WebViewController controller = WebViewController.fromPlatformCreationParams(params); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - )); - unawaited(controller.addJavaScriptChannel( + ); + + await controller.addJavaScriptChannel( 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { final double currentTime = double.parse(message.message); @@ -325,11 +334,11 @@ Future main() async { videoPlaying.complete(null); } }, - )); + ); if (controller.platform is AndroidWebViewController) { - unawaited((controller.platform as AndroidWebViewController) - .setMediaPlaybackRequiresUserGesture(false)); + await (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); } await controller.loadRequest( @@ -348,7 +357,9 @@ Future main() async { .runJavaScriptReturningResult('isFullScreen();') as bool; expect(fullScreen, false); }); - }); + }, + // TODO(bparrishMines): Stop skipping once https://github.com/flutter/flutter/issues/148487 is resolved + skip: true); group('Audio playback policy', () { late String audioTestBase64; @@ -395,14 +406,14 @@ Future main() async { WebViewController controller = WebViewController.fromPlatformCreationParams(params); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - )); + ); if (controller.platform is AndroidWebViewController) { - unawaited((controller.platform as AndroidWebViewController) - .setMediaPlaybackRequiresUserGesture(false)); + await (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); } await controller.loadRequest( @@ -420,17 +431,18 @@ Future main() async { pageLoaded = Completer(); controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), - )); - unawaited(controller.loadRequest( + ); + + await controller.loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), - )); + ); await tester.pumpWidget(WebViewWidget(controller: controller)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); await pageLoaded.future; isPaused = @@ -453,13 +465,13 @@ Future main() async { final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), - ))); - unawaited(controller.loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,$getTitleTestBase64'), )); + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$getTitleTestBase64'), + ); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -504,18 +516,18 @@ Future main() async { final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); ScrollPositionChange? recordedPosition; - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), - ))); - unawaited(controller.setOnScrollPositionChange( + )); + await controller.setOnScrollPositionChange( (ScrollPositionChange contentOffsetChange) { recordedPosition = contentOffsetChange; - })); + }); - unawaited(controller.loadRequest(Uri.parse( + await controller.loadRequest(Uri.parse( 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', - ))); + )); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -562,19 +574,19 @@ Future main() async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), onNavigationRequest: (NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, - ))); + )); await tester.pumpWidget(WebViewWidget(controller: controller)); - unawaited(controller.loadRequest(Uri.parse(blankPageEncoded))); + await controller.loadRequest(Uri.parse(blankPageEncoded)); await pageLoaded.future; // Wait for initial page load. @@ -591,13 +603,12 @@ Future main() async { Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( NavigationDelegate(onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); - }))); - unawaited( - controller.loadRequest(Uri.parse('https://www.notawebsite..com'))); + })); + await controller.loadRequest(Uri.parse('https://www.notawebsite..com')); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -612,16 +623,16 @@ Future main() async { final Completer pageFinishCompleter = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageFinishCompleter.complete(), onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, - ))); - unawaited(controller.loadRequest( - Uri.parse('data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+'), )); + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+'), + ); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -633,18 +644,18 @@ Future main() async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), onNavigationRequest: (NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; - }))); + })); await tester.pumpWidget(WebViewWidget(controller: controller)); - unawaited(controller.loadRequest(Uri.parse(blankPageEncoded))); + await controller.loadRequest(Uri.parse(blankPageEncoded)); await pageLoaded.future; // Wait for initial page load. @@ -661,12 +672,71 @@ Future main() async { expect(currentUrl, isNot(contains('youtube.com'))); }); + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final WebViewController controller = WebViewController(); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + final NavigationDelegate delegate = NavigationDelegate( + onHttpError: (HttpResponseError error) { + errorCompleter.complete(error); + }, + ); + unawaited(controller.setNavigationDelegate(delegate)); + + unawaited(controller.loadRequest( + Uri.parse('$prefixUrl/favicon.ico'), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.response?.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + const String testPage = ''' + + + + + + '''; + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final WebViewController controller = WebViewController(); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + final NavigationDelegate delegate = NavigationDelegate( + onPageFinished: pageFinishCompleter.complete, + onHttpError: (HttpResponseError error) { + errorCompleter.complete(error); + }, + ); + unawaited(controller.setNavigationDelegate(delegate)); + + unawaited(controller.loadHtmlString(testPage)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), onNavigationRequest: (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; @@ -674,11 +744,11 @@ Future main() async { const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; - }))); + })); await tester.pumpWidget(WebViewWidget(controller: controller)); - unawaited(controller.loadRequest(Uri.parse(blankPageEncoded))); + await controller.loadRequest(Uri.parse(blankPageEncoded)); await pageLoaded.future; // Wait for initial page load. @@ -694,11 +764,11 @@ Future main() async { final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), - ))); - unawaited(controller.loadRequest(Uri.parse(blankPageEncoded))); + )); + await controller.loadRequest(Uri.parse(blankPageEncoded)); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -725,9 +795,9 @@ Future main() async { ); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(navigationDelegate)); - unawaited(controller.loadRequest(Uri.parse(primaryUrl))); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(navigationDelegate); + await controller.loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -752,18 +822,16 @@ Future main() async { final Completer authRequested = Completer(); final WebViewController controller = WebViewController(); - unawaited( - controller.setNavigationDelegate( - NavigationDelegate( - onHttpAuthRequest: (HttpAuthRequest request) => - authRequested.complete(), - ), + await controller.setNavigationDelegate( + NavigationDelegate( + onHttpAuthRequest: (HttpAuthRequest request) => + authRequested.complete(), ), ); await tester.pumpWidget(WebViewWidget(controller: controller)); - unawaited(controller.loadRequest(Uri.parse(basicAuthUrl))); + await controller.loadRequest(Uri.parse(basicAuthUrl)); await expectLater(authRequested.future, completes); }); @@ -773,24 +841,20 @@ Future main() async { final WebViewController controller = WebViewController(); final Completer pageFinished = Completer(); - unawaited( - controller.setNavigationDelegate( - NavigationDelegate( - onHttpAuthRequest: (HttpAuthRequest request) => request.onProceed( - const WebViewCredential( - user: 'user', - password: 'password', - ), - ), - onPageFinished: (_) => pageFinished.complete(), - onWebResourceError: (_) => fail('Authentication failed'), + await controller.setNavigationDelegate(NavigationDelegate( + onHttpAuthRequest: (HttpAuthRequest request) => request.onProceed( + const WebViewCredential( + user: 'user', + password: 'password', ), ), - ); + onPageFinished: (_) => pageFinished.complete(), + onWebResourceError: (_) => fail('Authentication failed'), + )); await tester.pumpWidget(WebViewWidget(controller: controller)); - unawaited(controller.loadRequest(Uri.parse(basicAuthUrl))); + await controller.loadRequest(Uri.parse(basicAuthUrl)); await expectLater(pageFinished.future, completes); }); @@ -801,10 +865,10 @@ Future main() async { final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), - ))); + )); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -820,11 +884,11 @@ Future main() async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), - ))); - unawaited(controller.loadRequest(Uri.parse(primaryUrl))); + )); + await controller.loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); @@ -850,11 +914,11 @@ Future main() async { Completer pageLoadCompleter = Completer(); final WebViewController controller = WebViewController(); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setNavigationDelegate(NavigationDelegate( + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoadCompleter.complete(), - ))); - unawaited(controller.loadRequest(Uri.parse(primaryUrl))); + )); + await controller.loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test_legacy.dart similarity index 100% rename from packages/webview_flutter/webview_flutter/example/integration_test/legacy/webview_flutter_test.dart rename to packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test_legacy.dart diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index d0a3f965c4e..d24ec11424f 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -170,6 +170,9 @@ Page resource error: debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }, + onHttpError: (HttpResponseError error) { + debugPrint('Error occurred on page: ${error.response?.statusCode}'); + }, onUrlChange: (UrlChange change) { debugPrint('url change to ${change.url}'); }, diff --git a/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart b/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart index dfee9e6bd23..06a7bd31dd8 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart @@ -34,6 +34,7 @@ class _WebViewExampleState extends State { }, onPageStarted: (String url) {}, onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { diff --git a/packages/webview_flutter/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/webview_flutter/example/pubspec.yaml index 789d9498a0c..7c5eb72fa97 100644 --- a/packages/webview_flutter/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/example/pubspec.yaml @@ -17,8 +17,8 @@ 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: ../ - webview_flutter_android: ^3.15.0 - webview_flutter_wkwebview: ^3.12.0 + webview_flutter_android: ^3.16.0 + webview_flutter_wkwebview: ^3.13.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter/example/test/main_test.dart b/packages/webview_flutter/webview_flutter/example/test/main_test.dart index c4b43428ec9..4bb788578a1 100644 --- a/packages/webview_flutter/webview_flutter/example/test/main_test.dart +++ b/packages/webview_flutter/webview_flutter/example/test/main_test.dart @@ -121,4 +121,7 @@ class FakeNavigationDelegate extends PlatformNavigationDelegate { Future setOnHttpAuthRequest( HttpAuthRequestCallback handler, ) async {} + + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async {} } diff --git a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart index a490fb1ca8b..746caed140d 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart @@ -50,6 +50,7 @@ class NavigationDelegate { void Function(WebResourceError error)? onWebResourceError, void Function(UrlChange change)? onUrlChange, void Function(HttpAuthRequest request)? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, }) : this.fromPlatformCreationParams( const PlatformNavigationDelegateCreationParams(), onNavigationRequest: onNavigationRequest, @@ -59,6 +60,7 @@ class NavigationDelegate { onWebResourceError: onWebResourceError, onUrlChange: onUrlChange, onHttpAuthRequest: onHttpAuthRequest, + onHttpError: onHttpError, ); /// Constructs a [NavigationDelegate] from creation params for a specific @@ -102,6 +104,7 @@ class NavigationDelegate { void Function(WebResourceError error)? onWebResourceError, void Function(UrlChange change)? onUrlChange, void Function(HttpAuthRequest request)? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, }) : this.fromPlatform( PlatformNavigationDelegate(params), onNavigationRequest: onNavigationRequest, @@ -111,6 +114,7 @@ class NavigationDelegate { onWebResourceError: onWebResourceError, onUrlChange: onUrlChange, onHttpAuthRequest: onHttpAuthRequest, + onHttpError: onHttpError, ); /// Constructs a [NavigationDelegate] from a specific platform implementation. @@ -125,6 +129,7 @@ class NavigationDelegate { this.onWebResourceError, void Function(UrlChange change)? onUrlChange, HttpAuthRequestCallback? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, }) { if (onNavigationRequest != null) { platform.setOnNavigationRequest(onNavigationRequest!); @@ -147,6 +152,9 @@ class NavigationDelegate { if (onHttpAuthRequest != null) { platform.setOnHttpAuthRequest(onHttpAuthRequest); } + if (onHttpError != null) { + platform.setOnHttpError(onHttpError); + } } /// Implementation of [PlatformNavigationDelegate] for the current platform. diff --git a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart index 97f127def1a..e135e3c5bcb 100644 --- a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart @@ -5,6 +5,8 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' show HttpAuthRequest, + HttpResponseError, + HttpResponseErrorCallback, JavaScriptAlertDialogRequest, JavaScriptConfirmDialogRequest, JavaScriptConsoleMessage, @@ -28,6 +30,8 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte WebResourceError, WebResourceErrorCallback, WebResourceErrorType, + WebResourceRequest, + WebResourceResponse, WebViewCookie, WebViewCredential, WebViewPermissionResourceType, diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 6307d713f34..c48857425b2 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.7.0 +version: 4.8.0 environment: sdk: ^3.2.3 @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_android: ^3.15.0 + webview_flutter_android: ^3.16.0 webview_flutter_platform_interface: ^2.10.0 - webview_flutter_wkwebview: ^3.12.0 + webview_flutter_wkwebview: ^3.13.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart index c2bc6f230fa..64f5b16170d 100644 --- a/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart @@ -99,6 +99,18 @@ void main() { verify(delegate.platform.setOnHttpAuthRequest(onHttpAuthRequest)); }); + + test('onHttpError', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onHttpError(HttpResponseError error) {} + + final NavigationDelegate delegate = NavigationDelegate( + onHttpError: onHttpError, + ); + + verify(delegate.platform.setOnHttpError(onHttpError)); + }); }); } diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart index cf3dd5c89d3..907cc474cc8 100644 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart @@ -13,6 +13,8 @@ void main() { 'ensure webview_flutter.dart exports classes from platform interface', () { main_file.HttpAuthRequest; + main_file.HttpResponseError; + main_file.HttpResponseErrorCallback; main_file.JavaScriptConsoleMessage; main_file.JavaScriptLogLevel; main_file.JavaScriptMessage; @@ -34,6 +36,8 @@ void main() { main_file.WebViewCookie; main_file.WebViewCredential; main_file.WebResourceErrorType; + main_file.WebResourceRequest; + main_file.WebResourceResponse; main_file.UrlChange; }, ); diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 44f60d72f46..0e2f151dbe9 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,21 @@ +## 3.16.4 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 3.16.3 + +* Bumps androidx.webkit:webkit from 1.10.0 to 1.11.0. + +## 3.16.2 + +* Bumps androidx.webkit:webkit from 1.7.0 to 1.10.0. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 3.16.1 + +* Fixes iframe navigation being handled in the main frame when `NavigationDelegate.onNavigationRequest` is present. + ## 3.16.0 * Adds onReceivedHttpError WebViewClient callback to support diff --git a/packages/webview_flutter/webview_flutter_android/README.md b/packages/webview_flutter/webview_flutter_android/README.md index 1db03b3afdd..3b3d73306fe 100644 --- a/packages/webview_flutter/webview_flutter_android/README.md +++ b/packages/webview_flutter/webview_flutter_android/README.md @@ -30,7 +30,7 @@ See: This is the current default mode for versions <23. It ensures that the WebView will display and work as expected, at the cost of some performance. See: -* https://flutter.dev/docs/development/platform-integration/platform-views#performance +* https://docs.flutter.dev/platform-integration/android/platform-views#performance This can be configured for versions >=23 with `AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features @@ -89,7 +89,7 @@ dart run build_runner build --delete-conflicting-outputs If you would like to contribute to the plugin, check out our [contribution guide][5]. [1]: https://pub.dev/packages/webview_flutter -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://pub.dev/packages/pigeon [4]: https://pub.dev/packages/mockito [5]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index 1f6e33e5af6..9592d2dec0d 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -41,7 +41,7 @@ android { dependencies { implementation 'androidx.annotation:annotation:1.7.1' - implementation 'androidx.webkit:webkit:1.7.0' + implementation 'androidx.webkit:webkit:1.11.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/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java index f50b5bf7280..d59943414b7 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java @@ -7,7 +7,6 @@ import android.content.res.AssetManager; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.PluginRegistry; import java.io.IOException; /** Provides access to the assets registered as part of the App bundle. */ @@ -48,37 +47,6 @@ public String[] list(@NonNull String path) throws IOException { return assetManager.list(path); } - /** - * Provides access to assets using the {@link PluginRegistry.Registrar} for looking up file paths - * to Flutter assets. - * - * @deprecated The {@link RegistrarFlutterAssetManager} is for Flutter's v1 embedding. For - * instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit - * http://flutter.dev/go/android-plugin-migration - */ - @Deprecated - static class RegistrarFlutterAssetManager extends FlutterAssetManager { - final PluginRegistry.Registrar registrar; - - /** - * Constructs a new instance of the {@link RegistrarFlutterAssetManager}. - * - * @param assetManager Instance of Android's {@link AssetManager} used to access assets within - * the App bundle. - * @param registrar Instance of {@link io.flutter.plugin.common.PluginRegistry.Registrar} used - * to look up file paths to assets registered by Flutter. - */ - RegistrarFlutterAssetManager(AssetManager assetManager, PluginRegistry.Registrar registrar) { - super(assetManager); - this.registrar = registrar; - } - - @Override - public String getAssetFilePathByName(String name) { - return registrar.lookupKeyForAsset(name); - } - } - /** * Provides access to assets using the {@link FlutterPlugin.FlutterAssets} for looking up file * paths to Flutter assets. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java index a2ed499629c..fdc44827998 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java @@ -88,7 +88,10 @@ public void onReceivedError( public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { flutterApi.requestLoading(this, view, request, reply -> {}); - return returnValueForShouldOverrideUrlLoading; + + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; } // Legacy codepath for < 24; newer versions use the variant above. @@ -187,7 +190,10 @@ public void onReceivedError( public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { flutterApi.requestLoading(this, view, request, reply -> {}); - return returnValueForShouldOverrideUrlLoading; + + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; } // Legacy codepath for < Lollipop; newer versions use the variant above. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 7f026119f47..3c73a4e4c31 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -34,8 +34,6 @@ * Java platform implementation of the webview_flutter plugin. * *

Register this in an add to app scenario to gracefully handle activity and context changes. - * - *

Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead. */ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { @Nullable private InstanceManager instanceManager; @@ -48,35 +46,11 @@ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to * register it. * - *

THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE - * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least - * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with {@link - * #registerWith} to use this plugin with older Flutter versions. - * *

Registration should eventually be handled automatically by v2 of the * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694 */ public WebViewFlutterPlugin() {} - /** - * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} - * package. - * - *

Calling this automatically initializes the plugin. However plugins initialized this way - * won't react to changes in activity or context, unlike {@link WebViewFlutterPlugin}. - */ - @SuppressWarnings({"unused", "deprecation"}) - public static void registerWith( - @NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - new WebViewFlutterPlugin() - .setUp( - registrar.messenger(), - registrar.platformViewRegistry(), - registrar.activity(), - new FlutterAssetManager.RegistrarFlutterAssetManager( - registrar.context().getAssets(), registrar)); - } - private void setUp( BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java deleted file mode 100644 index fa0d27d2f5b..00000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java +++ /dev/null @@ -1,55 +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. - -package io.flutter.plugins.webviewflutter; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.AssetManager; -import java.io.IOException; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - -@SuppressWarnings("deprecation") -public class RegistrarFlutterAssetManagerTest { - @Mock AssetManager mockAssetManager; - @Mock io.flutter.plugin.common.PluginRegistry.Registrar mockRegistrar; - - io.flutter.plugins.webviewflutter.FlutterAssetManager.RegistrarFlutterAssetManager - testRegistrarFlutterAssetManager; - - @Before - public void setUp() { - mockAssetManager = mock(AssetManager.class); - mockRegistrar = mock(io.flutter.plugin.common.PluginRegistry.Registrar.class); - - testRegistrarFlutterAssetManager = - new io.flutter.plugins.webviewflutter.FlutterAssetManager.RegistrarFlutterAssetManager( - mockAssetManager, mockRegistrar); - } - - @Test - public void list() { - try { - when(mockAssetManager.list("test/path")) - .thenReturn(new String[] {"index.html", "styles.css"}); - String[] actualFilePaths = testRegistrarFlutterAssetManager.list("test/path"); - verify(mockAssetManager).list("test/path"); - assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths); - } catch (IOException ex) { - fail(); - } - } - - @Test - public void registrar_getAssetFilePathByName() { - testRegistrarFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4"); - verify(mockRegistrar).lookupKeyForAsset("sample_movie.mp4"); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java similarity index 72% rename from packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java rename to packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java index ea40742465f..fa165238f66 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java @@ -5,6 +5,8 @@ package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -28,15 +30,13 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -public class WebViewClientTest { +public class WebViewClientCompatImplTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public WebViewClientFlutterApiImpl mockFlutterApi; @Mock public WebView mockWebView; - @Mock public WebViewClientCompatImpl mockWebViewClient; - InstanceManager instanceManager; WebViewClientHostApiImpl hostApiImpl; WebViewClientCompatImpl webViewClient; @@ -51,7 +51,7 @@ public void setUp() { @NonNull public WebViewClient createWebViewClient( @NonNull WebViewClientFlutterApiImpl flutterApi) { - webViewClient = (WebViewClientCompatImpl) super.createWebViewClient(flutterApi); + webViewClient = new WebViewClientCompatImpl(flutterApi); return webViewClient; } }; @@ -93,6 +93,54 @@ public void urlLoading() { .urlLoading(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); } + @Test + public void urlLoadingForMainFrame() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(false); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(true); + + assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void urlLoadingForMainFrameWithOverride() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(true); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(true); + + assertTrue(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void urlLoadingNotForMainFrame() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(false); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(false); + + assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void urlLoadingNotForMainFrameWithOverride() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(true); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(false); + + assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + @Test public void convertWebResourceRequestWithNullHeaders() { final Uri mockUri = mock(Uri.class); @@ -111,6 +159,7 @@ public void convertWebResourceRequestWithNullHeaders() { @Test public void setReturnValueForShouldOverrideUrlLoading() { + WebViewClientHostApiImpl.WebViewClientCompatImpl mockWebViewClient = mock(); final WebViewClientHostApiImpl webViewClientHostApi = new WebViewClientHostApiImpl( instanceManager, diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java new file mode 100644 index 00000000000..cddd089ab4b --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java @@ -0,0 +1,190 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.Uri; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.annotation.NonNull; +import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCreator; +import java.util.HashMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class WebViewClientImplTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public WebViewClientFlutterApiImpl mockFlutterApi; + + @Mock public WebView mockWebView; + + InstanceManager instanceManager; + WebViewClientHostApiImpl hostApiImpl; + WebViewClientHostApiImpl.WebViewClientImpl webViewClient; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + + final WebViewClientCreator webViewClientCreator = + new WebViewClientCreator() { + @Override + @NonNull + public WebViewClient createWebViewClient( + @NonNull WebViewClientFlutterApiImpl flutterApi) { + webViewClient = new WebViewClientHostApiImpl.WebViewClientImpl(flutterApi); + return webViewClient; + } + }; + + hostApiImpl = + new WebViewClientHostApiImpl(instanceManager, webViewClientCreator, mockFlutterApi); + hostApiImpl.create(1L); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void onPageStarted() { + webViewClient.onPageStarted(mockWebView, "https://www.google.com", null); + verify(mockFlutterApi) + .onPageStarted(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); + } + + @Test + public void onReceivedError() { + webViewClient.onReceivedError(mockWebView, 32, "description", "https://www.google.com"); + verify(mockFlutterApi) + .onReceivedError( + eq(webViewClient), + eq(mockWebView), + eq(32L), + eq("description"), + eq("https://www.google.com"), + any()); + } + + @Test + public void urlLoading() { + webViewClient.shouldOverrideUrlLoading(mockWebView, "https://www.google.com"); + verify(mockFlutterApi) + .urlLoading(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); + } + + @Test + public void urlLoadingForMainFrame() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(false); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(true); + + assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void urlLoadingForMainFrameWithOverride() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(true); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(true); + + assertTrue(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void urlLoadingNotForMainFrame() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(false); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(false); + + assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void urlLoadingNotForMainFrameWithOverride() { + webViewClient.setReturnValueForShouldOverrideUrlLoading(true); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.isForMainFrame()).thenReturn(false); + + assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); + } + + @Test + public void convertWebResourceRequestWithNullHeaders() { + final Uri mockUri = mock(Uri.class); + when(mockUri.toString()).thenReturn(""); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.getMethod()).thenReturn("method"); + when(mockRequest.getUrl()).thenReturn(mockUri); + when(mockRequest.isForMainFrame()).thenReturn(true); + when(mockRequest.getRequestHeaders()).thenReturn(null); + + final GeneratedAndroidWebView.WebResourceRequestData data = + WebViewClientFlutterApiImpl.createWebResourceRequestData(mockRequest); + assertEquals(data.getRequestHeaders(), new HashMap()); + } + + @Test + public void doUpdateVisitedHistory() { + webViewClient.doUpdateVisitedHistory(mockWebView, "https://www.google.com", true); + verify(mockFlutterApi) + .doUpdateVisitedHistory( + eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), eq(true), any()); + } + + @Test + public void onReceivedHttpError() { + final Uri mockUri = mock(Uri.class); + when(mockUri.toString()).thenReturn(""); + + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.getMethod()).thenReturn("method"); + when(mockRequest.getUrl()).thenReturn(mockUri); + when(mockRequest.isForMainFrame()).thenReturn(true); + when(mockRequest.getRequestHeaders()).thenReturn(null); + + final WebResourceResponse mockResponse = mock(WebResourceResponse.class); + when(mockResponse.getStatusCode()).thenReturn(404); + + webViewClient.onReceivedHttpError(mockWebView, mockRequest, mockResponse); + verify(mockFlutterApi) + .onReceivedHttpError( + eq(webViewClient), + eq(mockWebView), + any(WebResourceRequest.class), + any(WebResourceResponse.class), + any()); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java index 0f0c1464e0e..21923b54698 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java +++ b/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -13,7 +13,7 @@ * Annotation to aid repository tooling in determining if a test is * a native java unit test or a java class with a dart integration. * - * See: https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests * for more infomation. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/AndroidManifest.xml b/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/AndroidManifest.xml index b3e1d11fded..8a28579c379 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/AndroidManifest.xml +++ b/packages/webview_flutter/webview_flutter_android/example/android/app/src/main/AndroidManifest.xml @@ -15,13 +15,6 @@ android:name="io.flutter.embedding.android.FlutterActivity" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize"> - - diff --git a/packages/webview_flutter/webview_flutter_android/example/android/build.gradle b/packages/webview_flutter/webview_flutter_android/example/android/build.gradle index f87c8c63983..32742c718a0 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { - // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' if (System.getenv().containsKey(artifactRepoKey)) { println "Using artifact hub" diff --git a/packages/webview_flutter/webview_flutter_android/example/android/settings.gradle b/packages/webview_flutter/webview_flutter_android/example/android/settings.gradle index 8d7543314fa..32735a3cfd4 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/settings.gradle +++ b/packages/webview_flutter/webview_flutter_android/example/android/settings.gradle @@ -14,7 +14,7 @@ plugins.each { name, path -> project(":$name").projectDir = pluginDirectory } -// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. buildscript { repositories { maven { diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index cee7d65f33f..4c22df1ff28 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -64,8 +64,8 @@ Future main() async { final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams(uri: Uri.parse(primaryUrl)), ); @@ -189,12 +189,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); await tester.pumpWidget(Builder( @@ -223,12 +223,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((String url) => pageLoads.add(url))); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((String url) => pageLoads.add(url)); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( uri: Uri.parse(headersUrl), @@ -258,12 +258,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); final Completer channelCompleter = Completer(); await controller.addJavaScriptChannel( @@ -331,13 +331,13 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setUserAgent('Custom_User_Agent1')); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setUserAgent('Custom_User_Agent1'); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse('about:blank'))); @@ -427,13 +427,13 @@ Future main() async { AndroidWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); AndroidNavigationDelegate delegate = AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( @@ -461,12 +461,12 @@ Future main() async { controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); delegate = AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( @@ -498,15 +498,15 @@ Future main() async { final AndroidWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); final AndroidNavigationDelegate delegate = AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); - unawaited(controller.addJavaScriptChannel( + await controller.addJavaScriptChannel( JavaScriptChannelParams( name: 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { @@ -517,7 +517,7 @@ Future main() async { } }, ), - )); + ); await controller.loadRequest( LoadRequestParams( @@ -553,20 +553,20 @@ Future main() async { final AndroidWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); final AndroidNavigationDelegate delegate = AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); - unawaited(controller.setCustomWidgetCallbacks(onHideCustomWidget: () { + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.setCustomWidgetCallbacks(onHideCustomWidget: () { fullscreenExited.complete(); }, onShowCustomWidget: (Widget webView, void Function() onHideCustomView) { fullscreenEntered.complete(); onHideCustomView(); - })); + }); await controller.loadRequest( LoadRequestParams( @@ -642,13 +642,13 @@ Future main() async { AndroidWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); AndroidNavigationDelegate delegate = AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( @@ -676,12 +676,12 @@ Future main() async { controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); delegate = AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( uri: Uri.parse( @@ -722,12 +722,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( @@ -789,16 +789,16 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); - unawaited(controller.setOnScrollPositionChange( + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.setOnScrollPositionChange( (ScrollPositionChange contentOffsetChange) { recordedPosition = contentOffsetChange; - })); + }); await controller.loadRequest( LoadRequestParams( @@ -860,19 +860,18 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited( - delegate.setOnNavigationRequest((NavigationRequest navigationRequest) { - return (navigationRequest.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }), - ); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await delegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams(uri: Uri.parse(blankPageEncoded)), @@ -903,16 +902,14 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited( - delegate.setOnWebResourceError((WebResourceError error) { - errorCompleter.complete(error); - }), - ); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnWebResourceError((WebResourceError error) { + errorCompleter.complete(error); + }); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')), @@ -945,19 +942,15 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited( - delegate.setOnPageFinished((_) => pageFinishCompleter.complete()), - ); - unawaited( - delegate.setOnWebResourceError((WebResourceError error) { - errorCompleter.complete(error); - }), - ); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageFinishCompleter.complete()); + await delegate.setOnWebResourceError((WebResourceError error) { + errorCompleter.complete(error); + }); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( uri: Uri.parse( @@ -985,17 +978,17 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnHttpError((HttpResponseError error) { + await delegate.setOnHttpError((HttpResponseError error) { errorCompleter.complete(error); - })); - unawaited(controller.setPlatformNavigationDelegate(delegate)); - unawaited(controller.loadRequest( + }); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')), - )); + ); await tester.pumpWidget(Builder( builder: (BuildContext context) { @@ -1028,18 +1021,18 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnHttpError((HttpResponseError error) { + await delegate.setOnHttpError((HttpResponseError error) { errorCompleter.complete(error); - })); - unawaited(delegate.setOnPageFinished( + }); + await delegate.setOnPageFinished( (_) => pageFinishCompleter.complete(), - )); - unawaited(controller.setPlatformNavigationDelegate(delegate)); - unawaited(controller.loadHtmlString(testPage)); + ); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadHtmlString(testPage); await tester.pumpWidget(Builder( builder: (BuildContext context) { @@ -1059,18 +1052,18 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(delegate + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await delegate .setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; - })); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + }); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); @@ -1104,20 +1097,20 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(delegate + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await delegate .setOnNavigationRequest((NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; - })); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + }); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); @@ -1145,12 +1138,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); @@ -1182,12 +1175,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); @@ -1298,12 +1291,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await tester.pumpWidget(Builder( builder: (BuildContext context) { @@ -1327,12 +1320,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); @@ -1399,13 +1392,12 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited( - delegate.setOnPageFinished((_) => pageLoadCompleter.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoadCompleter.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller.loadRequest( LoadRequestParams( uri: Uri.parse( @@ -1446,8 +1438,8 @@ Future main() async { final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ); - unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); - unawaited(controller.setPlatformNavigationDelegate(delegate)); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); await controller .loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); @@ -1490,17 +1482,15 @@ Future main() async { ); final Completer alertMessage = Completer(); - unawaited(controller.setOnJavaScriptAlertDialog( + await controller.setOnJavaScriptAlertDialog( (JavaScriptAlertDialogRequest request) async { alertMessage.complete(request.message); }, - )); - - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited( - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))), ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( @@ -1520,18 +1510,16 @@ Future main() async { ); final Completer confirmMessage = Completer(); - unawaited(controller.setOnJavaScriptConfirmDialog( + await controller.setOnJavaScriptConfirmDialog( (JavaScriptConfirmDialogRequest request) async { confirmMessage.complete(request.message); return true; }, - )); - - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited( - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))), ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( @@ -1550,17 +1538,15 @@ Future main() async { const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setOnJavaScriptTextInputDialog( + await controller.setOnJavaScriptTextInputDialog( (JavaScriptTextInputDialogRequest request) async { return 'return message'; }, - )); - - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); - unawaited( - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))), ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( @@ -1594,7 +1580,7 @@ Future main() async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ); - unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); await controller.setOnConsoleMessage((JavaScriptConsoleMessage message) { debugMessageReceived diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test_legacy.dart similarity index 100% rename from packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart rename to packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test_legacy.dart 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 0f2972f23ef..490e3d44f7a 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -23,10 +23,10 @@ const String kNavigationExamplePage = ''' Navigation Delegate Example

-The navigation delegate is set to block navigation to the youtube website. +The navigation delegate is set to block navigation to the pub.dev website.

diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 6665ea37158..a79d03efb4c 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the webview_flutter_android plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index f353532bc5d..3a410362af2 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -955,7 +955,7 @@ class AndroidWebViewWidgetCreationParams /// For most use cases, this flag should be set to false. Hybrid Composition /// can have performance costs but doesn't have the limitation of rendering to /// an Android SurfaceTexture. See - /// * https://flutter.dev/docs/development/platform-integration/platform-views#performance + /// * https://docs.flutter.dev/platform-integration/android/platform-views#performance /// * https://github.com/flutter/flutter/issues/104889 /// * https://github.com/flutter/flutter/issues/116954 /// @@ -1464,7 +1464,11 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { final LoadRequestCallback? onLoadRequest = _onLoadRequest; final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; - if (onNavigationRequest == null || onLoadRequest == null) { + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + if (!isForMainFrame || + onNavigationRequest == null || + onLoadRequest == null) { return; } diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 01add915842..5a283b9acfc 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,11 +2,11 @@ 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.16.0 +version: 3.16.4 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: @@ -32,4 +32,4 @@ dev_dependencies: topics: - html - webview - - webview-flutter \ No newline at end of file + - webview-flutter diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart index 85b99dec232..1adc7e64356 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -163,6 +163,66 @@ void main() { expect(callbackNavigationRequest, isNull); }); + test( + 'onNavigationRequest from requestLoading should be called when request is for main frame', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + androidNavigationDelegate.setOnLoadRequest((_) async {}); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(callbackNavigationRequest, isNotNull); + }); + + test( + 'onNavigationRequest from requestLoading should not be called when request is not for main frame', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + androidNavigationDelegate.setOnLoadRequest((_) async {}); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(callbackNavigationRequest, isNull); + }); + test( 'onLoadRequest from requestLoading should not be called when navigationRequestCallback is not specified', () { @@ -598,6 +658,7 @@ class CapturingWebChromeClient extends android_webview.WebChromeClient { }) : super.detached() { lastCreatedDelegate = this; } + static CapturingWebChromeClient lastCreatedDelegate = CapturingWebChromeClient(); } @@ -611,6 +672,7 @@ class CapturingDownloadListener extends android_webview.DownloadListener { }) : super.detached() { lastCreatedListener = this; } + static CapturingDownloadListener lastCreatedListener = CapturingDownloadListener(onDownloadStart: (_, __, ___, ____, _____) {}); } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md index e61480a5107..fbad8878042 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 2.10.0 diff --git a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml index 006140a5bab..e5eb3c6b901 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.10.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md index 8788082fe8e..87645f14d73 100644 --- a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 0.2.2+4 diff --git a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml index 82e758cfb9f..23754739bf4 100644 --- a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the webview_flutter_web plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_web/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/pubspec.yaml index ee2245d4210..484c795b4a7 100644 --- a/packages/webview_flutter/webview_flutter_web/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/pubspec.yaml @@ -5,8 +5,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.2.2+4 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" flutter: plugin: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/AUTHORS b/packages/webview_flutter/webview_flutter_wkwebview/AUTHORS index a0cf8d9f1ca..8625e8aaa7d 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/AUTHORS +++ b/packages/webview_flutter/webview_flutter_wkwebview/AUTHORS @@ -68,3 +68,4 @@ Maurits van Beusekom Antonino Di Natale Nick Bradshaw The Vinh Luong +LinXunFeng diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 70ba80cee6a..14f55957ad9 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.13.1 + +* Fixes `JSON.stringify()` cannot serialize cyclic structures. + ## 3.13.0 * Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the diff --git a/packages/webview_flutter/webview_flutter_wkwebview/README.md b/packages/webview_flutter/webview_flutter_wkwebview/README.md index 3cbec14339e..58f50ca90fc 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/README.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/README.md @@ -45,7 +45,7 @@ dart run build_runner build --delete-conflicting-outputs If you would like to contribute to the plugin, check out our [contribution guide][5]. [1]: https://pub.dev/packages/webview_flutter -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin +[2]: https://flutter.dev/to/endorsed-federated-plugin [3]: https://pub.dev/packages/pigeon [4]: https://pub.dev/packages/mockito [5]: https://github.com/flutter/packages/blob/main/CONTRIBUTING.md diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 80445b96fee..4a179696b79 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -74,7 +74,10 @@ Future main() async { final int gcIdentifier = await gcCompleter.future; expect(gcIdentifier, 0); - }, timeout: const Timeout(Duration(seconds: 10))); + }, + // TODO(bparrishMines): See https://github.com/flutter/flutter/issues/148345 + skip: true, + timeout: const Timeout(Duration(seconds: 10))); testWidgets( 'WKWebView is released by garbage collection', @@ -1522,6 +1525,64 @@ Future main() async { await expectLater( debugMessageReceived.future, completion('debug:Debug message')); }); + + testWidgets('can receive console log messages with cyclic object value', + (WidgetTester tester) async { + const String testPage = ''' + + + + WebResourceError test + + + + + '''; + + final Completer debugMessageReceived = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + await controller + .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) { + debugMessageReceived + .complete('${consoleMessage.level.name}:${consoleMessage.message}'); + }); + + await controller.loadHtmlString(testPage); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await expectLater( + debugMessageReceived.future, + completion( + 'log:{"obj1":{"name":"obj1"},"obj2":{"name":"obj2","obj1":{"name":"obj1"}}}'), + ); + }); }); } 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 b9e9386239e..c152f326259 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 @@ -630,21 +630,53 @@ class WebKitWebViewController extends PlatformWebViewController { } Future _injectConsoleOverride() { + // Within overrideScript, a series of console output methods such as + // console.log will be rewritten to pass the output content to the Flutter + // end. + // + // These output contents will first be serialized through JSON.stringify(), + // but if the output content contains cyclic objects, it will encounter the + // following error. + // TypeError: JSON.stringify cannot serialize cyclic structures. + // See https://github.com/flutter/flutter/issues/144535. + // + // Considering this is just looking at the logs printed via console.log, + // the cyclic object is not important, so remove it. + // Therefore, the replacer parameter of JSON.stringify() is used and the + // removeCyclicObject method is passed in to solve the error. const WKUserScript overrideScript = WKUserScript( ''' -function log(type, args) { - var message = Object.values(args) - .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString()) - .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars - .join(", "); - - var log = { - level: type, - message: message - }; - - window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log)); -} +var _flutter_webview_plugin_overrides = _flutter_webview_plugin_overrides || { + removeCyclicObject: function() { + const traversalStack = []; + return function (k, v) { + if (typeof v !== "object" || v === null) { return v; } + const currentParentObj = this; + while ( + traversalStack.length > 0 && + traversalStack[traversalStack.length - 1] !== currentParentObj + ) { + traversalStack.pop(); + } + if (traversalStack.includes(v)) { return; } + traversalStack.push(v); + return v; + }; + }, + log: function (type, args) { + var message = Object.values(args) + .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v, _flutter_webview_plugin_overrides.removeCyclicObject()) : v.toString()) + .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars + .join(", "); + + var log = { + level: type, + message: message + }; + + window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log)); + } +}; let originalLog = console.log; let originalInfo = console.info; @@ -652,11 +684,11 @@ let originalWarn = console.warn; let originalError = console.error; let originalDebug = console.debug; -console.log = function() { log("log", arguments); originalLog.apply(null, arguments) }; -console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) }; -console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) }; -console.error = function() { log("error", arguments); originalError.apply(null, arguments) }; -console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) }; +console.log = function() { _flutter_webview_plugin_overrides.log("log", arguments); originalLog.apply(null, arguments) }; +console.info = function() { _flutter_webview_plugin_overrides.log("info", arguments); originalInfo.apply(null, arguments) }; +console.warn = function() { _flutter_webview_plugin_overrides.log("warning", arguments); originalWarn.apply(null, arguments) }; +console.error = function() { _flutter_webview_plugin_overrides.log("error", arguments); originalError.apply(null, arguments) }; +console.debug = function() { _flutter_webview_plugin_overrides.log("debug", arguments); originalDebug.apply(null, arguments) }; window.addEventListener("error", function(e) { log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index bffa7eaf66a..51d28e2191c 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.13.0 +version: 3.13.1 environment: sdk: ^3.2.3 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 a28eca241ad..b5f731f9735 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 @@ -1378,19 +1378,37 @@ void main() { expect(overrideConsoleScript.injectionTime, WKUserScriptInjectionTime.atDocumentStart); expect(overrideConsoleScript.source, ''' -function log(type, args) { - var message = Object.values(args) - .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString()) - .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars - .join(", "); - - var log = { - level: type, - message: message - }; - - window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log)); -} +var _flutter_webview_plugin_overrides = _flutter_webview_plugin_overrides || { + removeCyclicObject: function() { + const traversalStack = []; + return function (k, v) { + if (typeof v !== "object" || v === null) { return v; } + const currentParentObj = this; + while ( + traversalStack.length > 0 && + traversalStack[traversalStack.length - 1] !== currentParentObj + ) { + traversalStack.pop(); + } + if (traversalStack.includes(v)) { return; } + traversalStack.push(v); + return v; + }; + }, + log: function (type, args) { + var message = Object.values(args) + .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v, _flutter_webview_plugin_overrides.removeCyclicObject()) : v.toString()) + .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars + .join(", "); + + var log = { + level: type, + message: message + }; + + window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log)); + } +}; let originalLog = console.log; let originalInfo = console.info; @@ -1398,11 +1416,11 @@ let originalWarn = console.warn; let originalError = console.error; let originalDebug = console.debug; -console.log = function() { log("log", arguments); originalLog.apply(null, arguments) }; -console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) }; -console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) }; -console.error = function() { log("error", arguments); originalError.apply(null, arguments) }; -console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) }; +console.log = function() { _flutter_webview_plugin_overrides.log("log", arguments); originalLog.apply(null, arguments) }; +console.info = function() { _flutter_webview_plugin_overrides.log("info", arguments); originalInfo.apply(null, arguments) }; +console.warn = function() { _flutter_webview_plugin_overrides.log("warning", arguments); originalWarn.apply(null, arguments) }; +console.error = function() { _flutter_webview_plugin_overrides.log("error", arguments); originalError.apply(null, arguments) }; +console.debug = function() { _flutter_webview_plugin_overrides.log("debug", arguments); originalDebug.apply(null, arguments) }; window.addEventListener("error", function(e) { log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno); diff --git a/packages/xdg_directories/CHANGELOG.md b/packages/xdg_directories/CHANGELOG.md index 06f96b25cd5..e4ab09dbf14 100644 --- a/packages/xdg_directories/CHANGELOG.md +++ b/packages/xdg_directories/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 1.0.4 diff --git a/packages/xdg_directories/example/pubspec.yaml b/packages/xdg_directories/example/pubspec.yaml index f643abedbfc..d189615d2f8 100644 --- a/packages/xdg_directories/example/pubspec.yaml +++ b/packages/xdg_directories/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the xdg_directories package. publish_to: 'none' environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: ">=3.16.0" dependencies: flutter: diff --git a/packages/xdg_directories/pubspec.yaml b/packages/xdg_directories/pubspec.yaml index cdb1c0d9190..1fd5edc4de0 100644 --- a/packages/xdg_directories/pubspec.yaml +++ b/packages/xdg_directories/pubspec.yaml @@ -5,7 +5,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.4 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 platforms: linux: diff --git a/script/configs/allowed_pinned_deps.yaml b/script/configs/allowed_pinned_deps.yaml index 71b9084f3f4..1bc4d26f1b4 100644 --- a/script/configs/allowed_pinned_deps.yaml +++ b/script/configs/allowed_pinned_deps.yaml @@ -1,5 +1,5 @@ # The list of external dependencies that are allowed as pinned dependencies. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#Dependencies # # All entries here should have an explanation for why they are here. diff --git a/script/configs/allowed_unpinned_deps.yaml b/script/configs/allowed_unpinned_deps.yaml index 84ff94a1101..974385ac146 100644 --- a/script/configs/allowed_unpinned_deps.yaml +++ b/script/configs/allowed_unpinned_deps.yaml @@ -1,5 +1,5 @@ # The list of external dependencies that are allowed as unpinned dependencies. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#Dependencies # # All entries here should have an explanation for why they are here, either # via being part of one of the default-allowed groups, or a specific reason. @@ -45,6 +45,7 @@ - meta - mime - path +- platform - shelf - shelf_static - source_gen diff --git a/script/configs/exclude_all_packages_app.yaml b/script/configs/exclude_all_packages_app.yaml index 040087f10f5..ca15045d59d 100644 --- a/script/configs/exclude_all_packages_app.yaml +++ b/script/configs/exclude_all_packages_app.yaml @@ -9,5 +9,5 @@ # NOTE: camera_android is semi-excluded via special casing in the repo tools. # See create_all_packages_app_command.dart. -# This is a permament entry, as it should never be a direct app dependency. +# This is a permanent entry, as it should never be a direct app dependency. - plugin_platform_interface diff --git a/script/configs/exclude_all_packages_app_wasm.yaml b/script/configs/exclude_all_packages_app_wasm.yaml new file mode 100644 index 00000000000..d10cdbf6ff2 --- /dev/null +++ b/script/configs/exclude_all_packages_app_wasm.yaml @@ -0,0 +1,15 @@ +# This list should be kept as short as possible, and things should remain here +# only as long as necessary, since in general the goal is for all of the latest +# versions of packages to be mutually compatible, and compilable with Wasm. + +# This is only used for wasm compilation. Once all packages in the repo have +# been migrated, remove this file and use `exclude_all_packages_app.yaml` only. + +# Packages that aren't migrated yet. +# https://github.com/flutter/flutter/issues/117022 +- camera +- webview_flutter + +# Dependencies are not migrated yet +# https://github.com/flutter/flutter/issues/148624 +- google_maps_flutter diff --git a/script/configs/still_requires_api_33_avd.yaml b/script/configs/still_requires_api_33_avd.yaml deleted file mode 100644 index 7368d3d16e0..00000000000 --- a/script/configs/still_requires_api_33_avd.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Running the somes tests from these packages on an AVD with Android 34 causes failures. -- webview_flutter diff --git a/script/tool/README.md b/script/tool/README.md index f482a85c84b..551836e9534 100644 --- a/script/tool/README.md +++ b/script/tool/README.md @@ -96,7 +96,7 @@ dart run script/tool/bin/flutter_plugin_tools.dart update-excerpts dart run script/tool/bin/flutter_plugin_tools.dart update-excerpts --packages package_name ``` -_See also: https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code_ +_See also: https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#readme-code_ ### Update CHANGELOG and Version @@ -153,9 +153,9 @@ generation (e.g., regenerating mocks when updating `mockito`). **Releases are automated for `flutter/packages`.** The manual procedure described here is _deprecated_, and should only be used when -the automated process fails. Please, read -[Releasing a Plugin or Package](https://github.com/flutter/flutter/wiki/Releasing-a-Plugin-or-Package) -on the Flutter Wiki first. +the automated process fails. Please read +[Releasing a Plugin or Package](https://github.com/flutter/flutter/blob/master/docs/ecosystem/release/README.md) +before using `publish`. ```sh cd diff --git a/script/tool/lib/src/build_examples_command.dart b/script/tool/lib/src/build_examples_command.dart index 0f78d39114f..d8d1613e06d 100644 --- a/script/tool/lib/src/build_examples_command.dart +++ b/script/tool/lib/src/build_examples_command.dart @@ -38,6 +38,9 @@ const String _flutterBuildTypeWindows = 'windows'; const String _flutterBuildTypeAndroidAlias = 'android'; +/// Key for Swift Package Manager. +const String _swiftPackageManagerFlag = 'swift-package-manager'; + /// A command to build the example applications for packages. class BuildExamplesCommand extends PackageLoopingCommand { /// Creates an instance of the build command. @@ -58,6 +61,7 @@ class BuildExamplesCommand extends PackageLoopingCommand { defaultsTo: '', help: 'Enables the given Dart SDK experiments.', ); + argParser.addFlag(_swiftPackageManagerFlag); } // Maps the switch this command uses to identify a platform to information @@ -111,6 +115,15 @@ class BuildExamplesCommand extends PackageLoopingCommand { 'single key "$_pluginToolsConfigGlobalKey" containing a list of build ' 'arguments.'; + /// Returns true if `--swift-package-manager` flag was passed along with + /// either `--ios` or `--macos`. + bool get usingSwiftPackageManager { + final List platformFlags = _platforms.keys.toList(); + return getBoolArg(_swiftPackageManagerFlag) && + (platformFlags.contains(platformIOS) || + platformFlags.contains(platformMacOS)); + } + @override Future initializeRun() async { final List platformFlags = _platforms.keys.toList(); @@ -121,6 +134,17 @@ class BuildExamplesCommand extends PackageLoopingCommand { 'were specified. At least one platform must be provided.'); throw ToolExit(_exitNoPlatformFlags); } + + // TODO(vashworth): Enable on stable once Swift Package Manager feature is + // available on stable. + if (usingSwiftPackageManager && + platform.environment['CHANNEL'] != 'stable') { + await processRunner.runAndStream( + flutterCommand, + ['config', '--enable-swift-package-manager'], + exitOnError: true, + ); + } } @override diff --git a/script/tool/lib/src/common/core.dart b/script/tool/lib/src/common/core.dart index cd3df27d2bd..c75432f98c2 100644 --- a/script/tool/lib/src/common/core.dart +++ b/script/tool/lib/src/common/core.dart @@ -33,6 +33,10 @@ const String platformWindows = 'windows'; /// Key for enable experiment. const String kEnableExperiment = 'enable-experiment'; +/// A String to add to comments on temporarily-added changes that should not +/// land (e.g., dependency overrides in federated plugin combination PRs). +const String kDoNotLandWarning = 'DO NOT MERGE'; + /// Target platforms supported by Flutter. // ignore: public_member_api_docs enum FlutterPlatform { android, ios, linux, macos, web, windows } @@ -73,6 +77,8 @@ final Map _dartSdkForFlutterSdk = { Version(3, 16, 6): Version(3, 2, 3), Version(3, 16, 9): Version(3, 2, 6), Version(3, 19, 0): Version(3, 3, 0), + Version(3, 19, 6): Version(3, 3, 4), + Version(3, 22, 0): Version(3, 4, 0), }; /// Returns the version of the Dart SDK that shipped with the given Flutter @@ -86,7 +92,7 @@ bool isPackage(FileSystemEntity entity) { return false; } // According to - // https://dart.dev/guides/libraries/create-library-packages#what-makes-a-library-package + // https://dart.dev/guides/libraries/create-packages#what-makes-a-library-package // a package must also have a `lib/` directory, but in practice that's not // always true. Some special cases (espresso, flutter_template_images, etc.) // don't have any source, so this deliberately doesn't check that there's a diff --git a/script/tool/lib/src/common/package_state_utils.dart b/script/tool/lib/src/common/package_state_utils.dart index 1d09052f5e6..316251d4f01 100644 --- a/script/tool/lib/src/common/package_state_utils.dart +++ b/script/tool/lib/src/common/package_state_utils.dart @@ -127,6 +127,7 @@ bool _isTestChange(List pathComponents) { pathComponents.contains('androidTest') || pathComponents.contains('RunnerTests') || pathComponents.contains('RunnerUITests') || + pathComponents.contains('Tests') || pathComponents.last == 'dart_test.yaml' || // Pigeon's custom platform tests. pathComponents.first == 'platform_tests'; diff --git a/script/tool/lib/src/common/xcode.dart b/script/tool/lib/src/common/xcode.dart index e26735df122..4a4bf03ba54 100644 --- a/script/tool/lib/src/common/xcode.dart +++ b/script/tool/lib/src/common/xcode.dart @@ -32,13 +32,21 @@ class Xcode { /// Runs an `xcodebuild` in [directory] with the given parameters. Future runXcodeBuild( - Directory directory, { + Directory exampleDirectory, + String platform, { List actions = const ['build'], required String workspace, required String scheme, String? configuration, List extraFlags = const [], }) { + File? disabledSandboxEntitlementFile; + if (actions.contains('test') && platform.toLowerCase() == 'macos') { + disabledSandboxEntitlementFile = _createDisabledSandboxEntitlementFile( + exampleDirectory.childDirectory(platform.toLowerCase()), + configuration ?? 'Debug', + ); + } final List args = [ _xcodeBuildCommand, ...actions, @@ -46,13 +54,15 @@ class Xcode { ...['-scheme', scheme], if (configuration != null) ...['-configuration', configuration], ...extraFlags, + if (disabledSandboxEntitlementFile != null) + 'CODE_SIGN_ENTITLEMENTS=${disabledSandboxEntitlementFile.path}', ]; final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}'; if (log) { print(completeTestCommand); } return processRunner.runAndStream(_xcRunCommand, args, - workingDir: directory); + workingDir: exampleDirectory); } /// Returns true if [project], which should be an .xcodeproj directory, @@ -156,4 +166,48 @@ class Xcode { } return null; } + + /// Finds and copies macOS entitlements file. In the copy, disables sandboxing. + /// If entitlements file is not found, returns null. + /// + /// As of macOS 14, testing a macOS sandbox app may prompt the user to grant + /// access to the app. To workaround this in CI, we create and use a entitlements + /// file with sandboxing disabled. See + /// https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox. + File? _createDisabledSandboxEntitlementFile( + Directory macOSDirectory, + String configuration, + ) { + final String entitlementDefaultFileName = + configuration == 'Release' ? 'Release' : 'DebugProfile'; + + final File entitlementFile = macOSDirectory + .childDirectory('Runner') + .childFile('$entitlementDefaultFileName.entitlements'); + + if (!entitlementFile.existsSync()) { + print('Unable to find entitlements file at ${entitlementFile.path}'); + return null; + } + + final String originalEntitlementFileContents = + entitlementFile.readAsStringSync(); + final File disabledSandboxEntitlementFile = macOSDirectory + .fileSystem.systemTempDirectory + .createTempSync('flutter_disable_sandbox_entitlement.') + .childFile( + '${entitlementDefaultFileName}WithDisabledSandboxing.entitlements'); + disabledSandboxEntitlementFile.createSync(recursive: true); + disabledSandboxEntitlementFile.writeAsStringSync( + originalEntitlementFileContents.replaceAll( + RegExp( + r'com\.apple\.security\.app-sandbox<\/key>[\S\s]*?'), + ''' +com.apple.security.app-sandbox + ''', + ), + ); + + return disabledSandboxEntitlementFile; + } } diff --git a/script/tool/lib/src/create_all_packages_app_command.dart b/script/tool/lib/src/create_all_packages_app_command.dart index 380ced430b6..ee1804fc142 100644 --- a/script/tool/lib/src/create_all_packages_app_command.dart +++ b/script/tool/lib/src/create_all_packages_app_command.dart @@ -429,7 +429,7 @@ dev_dependencies:${_pubspecMapString(pubspec.devDependencies)} _adjustFile( pbxprojFile, replacements: >{ - // iOS 14 is required by google_maps_fluter. + // iOS 14 is required by google_maps_flutter. 'IPHONEOS_DEPLOYMENT_TARGET': [ ' IPHONEOS_DEPLOYMENT_TARGET = 14.0;' ], diff --git a/script/tool/lib/src/dart_test_command.dart b/script/tool/lib/src/dart_test_command.dart index 1c188ff38ad..b782abd1102 100644 --- a/script/tool/lib/src/dart_test_command.dart +++ b/script/tool/lib/src/dart_test_command.dart @@ -11,6 +11,15 @@ import 'common/plugin_utils.dart'; import 'common/pub_utils.dart'; import 'common/repository_package.dart'; +const int _exitUnknownTestPlatform = 3; + +enum _TestPlatform { + // Must run in the command-line VM. + vm, + // Must run in a browser. + browser, +} + /// A command to run Dart unit tests for packages. class DartTestCommand extends PackageLoopingCommand { /// Creates an instance of the test command. @@ -76,7 +85,7 @@ class DartTestCommand extends PackageLoopingCommand { return PackageResult.skip( "Non-web plugin tests don't need web testing."); } - if (_requiresVM(package)) { + if (_testOnTarget(package) == _TestPlatform.vm) { // This explict skip is necessary because trying to run tests in a mode // that the package has opted out of returns a non-zero exit code. return PackageResult.skip('Package has opted out of non-vm testing.'); @@ -85,7 +94,8 @@ class DartTestCommand extends PackageLoopingCommand { if (isWebOnlyPluginImplementation) { return PackageResult.skip("Web plugin tests don't need vm testing."); } - if (_requiresNonVM(package)) { + final _TestPlatform? target = _testOnTarget(package); + if (target != null && _testOnTarget(package) != _TestPlatform.vm) { // This explict skip is necessary because trying to run tests in a mode // that the package has opted out of returns a non-zero exit code. return PackageResult.skip('Package has opted out of vm testing.'); @@ -102,7 +112,8 @@ class DartTestCommand extends PackageLoopingCommand { final String? webRenderer = (platform == 'chrome') ? 'html' : null; bool passed; if (package.requiresFlutter()) { - passed = await _runFlutterTests(package, platform: platform, webRenderer: webRenderer); + passed = await _runFlutterTests(package, + platform: platform, webRenderer: webRenderer); } else { passed = await _runDartTests(package, platform: platform); } @@ -156,34 +167,39 @@ class DartTestCommand extends PackageLoopingCommand { return exitCode == 0; } - bool _requiresVM(RepositoryPackage package) { - final File testConfig = package.directory.childFile('dart_test.yaml'); - if (!testConfig.existsSync()) { - return false; - } - // test_on lines can be very complex, but in pratice the packages in this - // repo currently only need the ability to require vm or not, so that - // simple directive is all that is currently supported. - final RegExp vmRequrimentRegex = RegExp(r'^test_on:\s*vm$'); - return testConfig - .readAsLinesSync() - .any((String line) => vmRequrimentRegex.hasMatch(line)); - } - - bool _requiresNonVM(RepositoryPackage package) { + /// Returns the required test environment, or null if none is specified. + /// + /// Throws if the target is not recognized. + _TestPlatform? _testOnTarget(RepositoryPackage package) { final File testConfig = package.directory.childFile('dart_test.yaml'); if (!testConfig.existsSync()) { - return false; + return null; } - // test_on lines can be very complex, but in pratice the packages in this - // repo currently only need the ability to require vm or not, so a simple - // one-target directive is all that's supported currently. Making it - // deliberately strict avoids the possibility of accidentally skipping vm - // coverage due to a complex expression that's not handled correctly. - final RegExp testOnRegex = RegExp(r'^test_on:\s*([a-z])*\s*$'); - return testConfig.readAsLinesSync().any((String line) { + final RegExp testOnRegex = RegExp(r'^test_on:\s*([a-z].*[a-z])\s*$'); + for (final String line in testConfig.readAsLinesSync()) { final RegExpMatch? match = testOnRegex.firstMatch(line); - return match != null && match.group(1) != 'vm'; - }); + if (match != null) { + final String targetFilter = match.group(1)!; + // test_on lines can be very complex, but in pratice the packages in + // this repo currently only need the ability to require vm or not, so a + // simple one-target directive is all that's supported currently. + // Making it deliberately strict avoids the possibility of accidentally + // skipping vm coverage due to a complex expression that's not handled + // correctly. + switch (targetFilter) { + case 'vm': + return _TestPlatform.vm; + case 'browser': + return _TestPlatform.browser; + default: + printError('Unknown "test_on" value: "$targetFilter"\n' + "If this value needs to be supported for this package's tests, " + 'please update the repository tooling to support more test_on ' + 'modes.'); + throw ToolExit(_exitUnknownTestPlatform); + } + } + } + return null; } } diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart index f3baa5c7cee..2dc563dbaf8 100644 --- a/script/tool/lib/src/drive_examples_command.dart +++ b/script/tool/lib/src/drive_examples_command.dart @@ -16,7 +16,7 @@ import 'common/repository_package.dart'; const int _exitNoPlatformFlags = 2; const int _exitNoAvailableDevice = 3; -// From https://docs.flutter.dev/testing/integration-tests#running-in-a-browser +// From https://flutter.dev/to/integration-test-on-web const int _chromeDriverPort = 4444; /// A command to run the integration tests for a package's example applications. diff --git a/script/tool/lib/src/federation_safety_check_command.dart b/script/tool/lib/src/federation_safety_check_command.dart index 804ef8e3abc..477fe4a2f61 100644 --- a/script/tool/lib/src/federation_safety_check_command.dart +++ b/script/tool/lib/src/federation_safety_check_command.dart @@ -6,6 +6,7 @@ import 'package:file/file.dart'; import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; +import 'common/core.dart'; import 'common/file_utils.dart'; import 'common/git_version_finder.dart'; import 'common/output_utils.dart'; @@ -112,6 +113,21 @@ class FederationSafetyCheckCommand extends PackageLoopingCommand { 'Platform interface changes are not validated.'); } + // Special-case combination PRs that are following repo process, so that + // they don't get an error that makes it sound like something is wrong with + // the PR (but is still an error so that the PR can't land without following + // the resolution process). + if (package.getExamples().any(_hasTemporaryDependencyOverrides)) { + printError('"$kDoNotLandWarning" found in pubspec.yaml, so this is ' + 'assumed to be the initial combination PR for a federated change, ' + 'following the standard repository procedure. This failure is ' + 'expected, in order to prevent accidentally landing the temporary ' + 'overrides, and will automatically be resolved when the temporary ' + 'overrides are replaced by dependency version bumps later in the ' + 'process.'); + return PackageResult.fail(['Unresolved combo PR.']); + } + // Uses basename to match _changedPackageFiles. final String basePackageName = package.directory.parent.basename; final String platformInterfacePackageName = @@ -216,4 +232,9 @@ class FederationSafetyCheckCommand extends PackageLoopingCommand { // against having the wrong (e.g., incorrectly empty) diff output. return foundComment; } + + bool _hasTemporaryDependencyOverrides(RepositoryPackage package) { + final String pubspecContents = package.pubspecFile.readAsStringSync(); + return pubspecContents.contains(kDoNotLandWarning); + } } diff --git a/script/tool/lib/src/gradle_check_command.dart b/script/tool/lib/src/gradle_check_command.dart index e1af42fd724..38977d91c98 100644 --- a/script/tool/lib/src/gradle_check_command.dart +++ b/script/tool/lib/src/gradle_check_command.dart @@ -133,7 +133,7 @@ class GradleCheckCommand extends PackageLoopingCommand { /// Documentation url for Artifact hub implementation in flutter repo's. @visibleForTesting static const String artifactHubDocumentationString = - r'https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure'; + r'https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure'; /// String printed as example of valid example root build.gradle repository /// configuration that enables artifact hub env variable. @@ -158,7 +158,7 @@ class GradleCheckCommand extends PackageLoopingCommand { final RegExp keyPresentRegex = RegExp('$keyVariable' r"\s+=\s+'ARTIFACT_HUB_REPOSITORY'"); final RegExp documentationPresentRegex = RegExp( - r'github\.com.*wiki.*Plugins-and-Packages-repository-structure.*gradle-structure'); + r'github\.com.*flutter.*blob.*Plugins-and-Packages-repository-structure.*gradle-structure'); final RegExp keyReadRegex = RegExp(r'if.*System\.getenv.*\.containsKey.*' '$keyVariable'); final RegExp keyUsedRegex = @@ -223,7 +223,7 @@ apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" bool _validateArtifactHubSettingsUsage( RepositoryPackage example, List gradleLines) { final RegExp documentationPresentRegex = RegExp( - r'github\.com.*wiki.*Plugins-and-Packages-repository-structure.*gradle-structure'); + r'github\.com.*flutter.*blob.*Plugins-and-Packages-repository-structure.*gradle-structure'); final RegExp artifactRegistryDefinitionRegex = RegExp( r'classpath.*gradle\.plugin\.com\.google\.cloud\.artifactregistry:artifactregistry-gradle-plugin'); final RegExp artifactRegistryPluginApplyRegex = RegExp( diff --git a/script/tool/lib/src/make_deps_path_based_command.dart b/script/tool/lib/src/make_deps_path_based_command.dart index 6ebc1d313a6..b3682ea6f13 100644 --- a/script/tool/lib/src/make_deps_path_based_command.dart +++ b/script/tool/lib/src/make_deps_path_based_command.dart @@ -57,8 +57,8 @@ class MakeDepsPathBasedCommand extends PackageCommand { // Includes a reference to the docs so that reviewers who aren't familiar with // the federated plugin change process don't think it's a mistake. static const String _dependencyOverrideWarningComment = - '# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.\n' - '# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins'; + '# FOR TESTING AND INITIAL REVIEW ONLY. $kDoNotLandWarning.\n' + '# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins'; @override final String name = 'make-deps-path-based'; diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart index aefb4851344..53bf41298d8 100644 --- a/script/tool/lib/src/native_test_command.dart +++ b/script/tool/lib/src/native_test_command.dart @@ -30,7 +30,7 @@ const String misconfiguredJavaIntegrationTestErrorExplanation = 'The following files use @RunWith(FlutterTestRunner.class) ' 'but not @DartIntegrationTest, which will cause hangs when run with ' 'this command. See ' - 'https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests ' + 'https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests ' 'for instructions.'; /// The command to run native tests for plugins: @@ -465,7 +465,10 @@ this command. _printRunningExampleTestsMessage(example, platform); final int exitCode = await _xcode.runXcodeBuild( example.directory, - actions: ['test'], + platform, + // Clean before testing to remove cached swiftmodules from previous + // runs, which can cause conflicts. + actions: ['clean', 'test'], workspace: '${platform.toLowerCase()}/Runner.xcworkspace', scheme: 'Runner', configuration: 'Debug', diff --git a/script/tool/lib/src/podspec_check_command.dart b/script/tool/lib/src/podspec_check_command.dart index b0982b7ee69..4f1b5e0dfa5 100644 --- a/script/tool/lib/src/podspec_check_command.dart +++ b/script/tool/lib/src/podspec_check_command.dart @@ -161,8 +161,19 @@ class PodspecCheckCommand extends PackageLoopingCommand { } /// Returns true if there is any iOS plugin implementation code written in - /// Swift. + /// Swift. Skips files named "Package.swift", which is a Swift Pacakge Manager + /// manifest file and does not mean the plugin is written in Swift. Future _hasIOSSwiftCode(RepositoryPackage package) async { + final String iosSwiftPackageManifestPath = package + .platformDirectory(FlutterPlatform.ios) + .childDirectory(package.directory.basename) + .childFile('Package.swift') + .path; + final String darwinSwiftPackageManifestPath = package.directory + .childDirectory('darwin') + .childDirectory(package.directory.basename) + .childFile('Package.swift') + .path; return getFilesForPackage(package).any((File entity) { final String relativePath = getRelativePosixPath(entity, from: package.directory); @@ -170,8 +181,16 @@ class PodspecCheckCommand extends PackageLoopingCommand { if (relativePath.startsWith('example/')) { return false; } + // Ignore test code. + if (relativePath.contains('/Tests/') || + relativePath.contains('/RunnerTests/') || + relativePath.contains('/RunnerUITests/')) { + return false; + } final String filePath = entity.path; - return path.extension(filePath) == '.swift'; + return filePath != iosSwiftPackageManifestPath && + filePath != darwinSwiftPackageManifestPath && + path.extension(filePath) == '.swift'; }); } diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart index 90f9324fd20..34aa3813a41 100644 --- a/script/tool/lib/src/pubspec_check_command.dart +++ b/script/tool/lib/src/pubspec_check_command.dart @@ -473,7 +473,7 @@ class PubspecCheckCommand extends PackageLoopingCommand { Version? minMinFlutterVersion, }) { String unknownDartVersionError(Version flutterVersion) { - return 'Dart SDK version for Fluter SDK version ' + return 'Dart SDK version for Flutter SDK version ' '$flutterVersion is unknown. ' 'Please update the map for getDartSdkForFlutterSdk with the ' 'corresponding Dart version.'; @@ -583,7 +583,7 @@ class PubspecCheckCommand extends PackageLoopingCommand { ''' The following unexpected non-local dependencies were found: ${badDependencies.map((String name) => ' $name').join('\n')} -Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies +Please see https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#Dependencies for more information and next steps. ''', if (misplacedDevDependencies.isNotEmpty) diff --git a/script/tool/lib/src/readme_check_command.dart b/script/tool/lib/src/readme_check_command.dart index a0d8d850194..cc2ede24355 100644 --- a/script/tool/lib/src/readme_check_command.dart +++ b/script/tool/lib/src/readme_check_command.dart @@ -10,8 +10,8 @@ import 'common/output_utils.dart'; import 'common/package_looping_command.dart'; import 'common/repository_package.dart'; -const String _instructionWikiUrl = - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages'; +const String _instructionUrl = + 'https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md'; /// A command to enforce README conventions across the repository. class ReadmeCheckCommand extends PackageLoopingCommand { @@ -180,7 +180,7 @@ class ReadmeCheckCommand extends PackageLoopingCommand { printError( '\n${indentation}For each block listed above, add ' 'tag on the previous line, as explained at\n' - '$_instructionWikiUrl'); + '$_instructionUrl'); errorSummary ??= 'Missing code-excerpt management for code block'; } diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart index da629a45345..4b4147182b1 100644 --- a/script/tool/lib/src/update_excerpts_command.dart +++ b/script/tool/lib/src/update_excerpts_command.dart @@ -92,7 +92,7 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { 'the resulting changes.\n' '\n' '${indentation}For more information, see ' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code', + 'https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#readme-code', ); return PackageResult.fail(); } diff --git a/script/tool/lib/src/update_min_sdk_command.dart b/script/tool/lib/src/update_min_sdk_command.dart index c1c86e8f2cd..429036a47ef 100644 --- a/script/tool/lib/src/update_min_sdk_command.dart +++ b/script/tool/lib/src/update_min_sdk_command.dart @@ -45,7 +45,7 @@ class UpdateMinSdkCommand extends PackageLoopingCommand { _flutterMinVersion = Version.parse(getStringArg(_flutterMinFlag)); final Version? dartMinVersion = getDartSdkForFlutterSdk(_flutterMinVersion); if (dartMinVersion == null) { - printError('Dart SDK version for Fluter SDK version ' + printError('Dart SDK version for Flutter SDK version ' '$_flutterMinVersion is unknown. ' 'Please update the map for getDartSdkForFlutterSdk with the ' 'corresponding Dart version.'); diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index 0a8111c813f..58ad880cbae 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -358,7 +358,7 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} '${indentation}Breaking changes to platform interfaces are not ' 'allowed without explicit justification.\n' '${indentation}See ' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' + 'https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md ' 'for more information.'); return _CurrentVersionState.invalidChange; } diff --git a/script/tool/lib/src/xcode_analyze_command.dart b/script/tool/lib/src/xcode_analyze_command.dart index e753ccb21fc..707a3f08b6d 100644 --- a/script/tool/lib/src/xcode_analyze_command.dart +++ b/script/tool/lib/src/xcode_analyze_command.dart @@ -111,7 +111,10 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand { print('Running $platform tests and analyzer for $examplePath...'); final int exitCode = await _xcode.runXcodeBuild( example.directory, - actions: ['analyze'], + platform, + // Clean before analyzing to remove cached swiftmodules from previous + // runs, which can cause conflicts. + actions: ['clean', 'analyze'], workspace: '${platform.toLowerCase()}/Runner.xcworkspace', scheme: 'Runner', configuration: 'Debug', diff --git a/script/tool/test/build_examples_command_test.dart b/script/tool/test/build_examples_command_test.dart index 2f58dac87c8..0c99bfb9ba3 100644 --- a/script/tool/test/build_examples_command_test.dart +++ b/script/tool/test/build_examples_command_test.dart @@ -164,6 +164,98 @@ void main() { ])); }); + test('building for iOS with Swift Package Manager on master channel', + () async { + mockPlatform.isMacOS = true; + mockPlatform.environment['CHANNEL'] = 'master'; + + final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, + platformSupport: { + platformIOS: const PlatformDetails(PlatformSupport.inline), + }); + + final Directory pluginExampleDirectory = getExampleDir(plugin); + + final List output = await runCapturingPrint(runner, [ + 'build-examples', + '--ios', + '--enable-experiment=exp1', + '--swift-package-manager', + ]); + + expect( + output, + containsAllInOrder([ + '\nBUILDING plugin/example for iOS', + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + getFlutterCommand(mockPlatform), + const ['config', '--enable-swift-package-manager'], + null, + ), + ProcessCall( + getFlutterCommand(mockPlatform), + const [ + 'build', + 'ios', + '--no-codesign', + '--enable-experiment=exp1' + ], + pluginExampleDirectory.path, + ), + ]), + ); + }); + + test( + 'building for iOS with Swift Package Manager on stable channel does not enable SPM', + () async { + mockPlatform.isMacOS = true; + mockPlatform.environment['CHANNEL'] = 'stable'; + + final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, + platformSupport: { + platformIOS: const PlatformDetails(PlatformSupport.inline), + }); + + final Directory pluginExampleDirectory = getExampleDir(plugin); + + final List output = await runCapturingPrint(runner, [ + 'build-examples', + '--ios', + '--enable-experiment=exp1', + '--swift-package-manager', + ]); + + expect( + output, + containsAllInOrder([ + '\nBUILDING plugin/example for iOS', + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + getFlutterCommand(mockPlatform), + const [ + 'build', + 'ios', + '--no-codesign', + '--enable-experiment=exp1' + ], + pluginExampleDirectory.path, + ), + ]), + ); + }); + test( 'building for Linux when plugin is not set up for Linux results in no-op', () async { @@ -261,6 +353,86 @@ void main() { ])); }); + test('building for macOS with Swift Package Manager on master channel', + () async { + mockPlatform.isMacOS = true; + mockPlatform.environment['CHANNEL'] = 'master'; + + final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, + platformSupport: { + platformMacOS: const PlatformDetails(PlatformSupport.inline), + }); + + final Directory pluginExampleDirectory = getExampleDir(plugin); + + final List output = await runCapturingPrint(runner, + ['build-examples', '--macos', '--swift-package-manager']); + + expect( + output, + containsAllInOrder([ + '\nBUILDING plugin/example for macOS', + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + getFlutterCommand(mockPlatform), + const ['config', '--enable-swift-package-manager'], + null, + ), + ProcessCall( + getFlutterCommand(mockPlatform), + const [ + 'build', + 'macos', + ], + pluginExampleDirectory.path, + ), + ]), + ); + }); + + test( + 'building for macOS with Swift Package Manager on stable channel does not enable SPM', + () async { + mockPlatform.isMacOS = true; + mockPlatform.environment['CHANNEL'] = 'stable'; + + final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, + platformSupport: { + platformMacOS: const PlatformDetails(PlatformSupport.inline), + }); + + final Directory pluginExampleDirectory = getExampleDir(plugin); + + final List output = await runCapturingPrint(runner, + ['build-examples', '--macos', '--swift-package-manager']); + + expect( + output, + containsAllInOrder([ + '\nBUILDING plugin/example for macOS', + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + getFlutterCommand(mockPlatform), + const [ + 'build', + 'macos', + ], + pluginExampleDirectory.path, + ), + ]), + ); + }); + test('building for web with no implementation results in no-op', () async { createFakePlugin('plugin', packagesDir); diff --git a/script/tool/test/common/xcode_test.dart b/script/tool/test/common/xcode_test.dart index b401287e15a..8fd41516001 100644 --- a/script/tool/test/common/xcode_test.dart +++ b/script/tool/test/common/xcode_test.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/local.dart'; +import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/xcode.dart'; import 'package:test/test.dart'; @@ -161,6 +162,7 @@ void main() { final int exitCode = await xcode.runXcodeBuild( directory, + 'ios', workspace: 'A.xcworkspace', scheme: 'AScheme', ); @@ -186,7 +188,7 @@ void main() { test('handles all arguments', () async { final Directory directory = const LocalFileSystem().currentDirectory; - final int exitCode = await xcode.runXcodeBuild(directory, + final int exitCode = await xcode.runXcodeBuild(directory, 'ios', actions: ['action1', 'action2'], workspace: 'A.xcworkspace', scheme: 'AScheme', @@ -225,6 +227,7 @@ void main() { final int exitCode = await xcode.runXcodeBuild( directory, + 'ios', workspace: 'A.xcworkspace', scheme: 'AScheme', ); @@ -246,6 +249,42 @@ void main() { directory.path), ])); }); + + test('sets CODE_SIGN_ENTITLEMENTS for macos tests', () async { + final FileSystem fileSystem = MemoryFileSystem(); + final Directory directory = fileSystem.currentDirectory; + directory + .childDirectory('macos') + .childDirectory('Runner') + .childFile('DebugProfile.entitlements') + .createSync(recursive: true); + + final int exitCode = await xcode.runXcodeBuild( + directory, + 'macos', + workspace: 'A.xcworkspace', + scheme: 'AScheme', + actions: ['test'], + ); + + expect(exitCode, 0); + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + 'xcrun', + const [ + 'xcodebuild', + 'test', + '-workspace', + 'A.xcworkspace', + '-scheme', + 'AScheme', + 'CODE_SIGN_ENTITLEMENTS=/.tmp_rand0/flutter_disable_sandbox_entitlement.rand0/DebugProfileWithDisabledSandboxing.entitlements' + ], + directory.path), + ])); + }); }); group('projectHasTarget', () { diff --git a/script/tool/test/dart_test_command_test.dart b/script/tool/test/dart_test_command_test.dart index d56bb44b0fb..adbaf782bd3 100644 --- a/script/tool/test/dart_test_command_test.dart +++ b/script/tool/test/dart_test_command_test.dart @@ -249,6 +249,68 @@ void main() { ); }); + test('throws for an unrecognized test_on type', () async { + final RepositoryPackage package = createFakePackage( + 'a_package', + packagesDir, + extraFiles: ['test/empty_test.dart'], + ); + package.directory.childFile('dart_test.yaml').writeAsStringSync(''' +test_on: unknown +'''); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['dart-test', '--platform=vm'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains('Unknown "test_on" value: "unknown"\n' + "If this value needs to be supported for this package's " + 'tests, please update the repository tooling to support more ' + 'test_on modes.'), + ], + )); + }); + + test('throws for an valid but complex test_on directive', () async { + final RepositoryPackage package = createFakePackage( + 'a_package', + packagesDir, + extraFiles: ['test/empty_test.dart'], + ); + package.directory.childFile('dart_test.yaml').writeAsStringSync(''' +test_on: vm && browser +'''); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['dart-test', '--platform=vm'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains('Unknown "test_on" value: "vm && browser"\n' + "If this value needs to be supported for this package's " + 'tests, please update the repository tooling to support more ' + 'test_on modes.'), + ], + )); + }); + test('runs in Chrome when requested for Flutter package', () async { final RepositoryPackage package = createFakePackage( 'a_package', @@ -265,7 +327,12 @@ void main() { orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome', '--web-renderer=html'], + const [ + 'test', + '--color', + '--platform=chrome', + '--web-renderer=html' + ], package.path), ]), ); @@ -289,7 +356,12 @@ void main() { orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome', '--web-renderer=html'], + const [ + 'test', + '--color', + '--platform=chrome', + '--web-renderer=html' + ], plugin.path), ]), ); @@ -314,7 +386,12 @@ void main() { orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome', '--web-renderer=html'], + const [ + 'test', + '--color', + '--platform=chrome', + '--web-renderer=html' + ], plugin.path), ]), ); @@ -339,7 +416,12 @@ void main() { orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome', '--web-renderer=html'], + const [ + 'test', + '--color', + '--platform=chrome', + '--web-renderer=html' + ], plugin.path), ]), ); @@ -409,7 +491,12 @@ void main() { orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome', '--web-renderer=html'], + const [ + 'test', + '--color', + '--platform=chrome', + '--web-renderer=html' + ], plugin.path), ]), ); @@ -459,6 +546,30 @@ test_on: vm ); }); + test('does not skip running vm in vm mode', () async { + final RepositoryPackage package = createFakePackage( + 'a_package', + packagesDir, + extraFiles: ['test/empty_test.dart'], + ); + package.directory.childFile('dart_test.yaml').writeAsStringSync(''' +test_on: vm +'''); + + final List output = await runCapturingPrint( + runner, ['dart-test', '--platform=vm']); + + expect( + output, + isNot(containsAllInOrder([ + contains('Package has opted out'), + ]))); + expect( + processRunner.recordedCalls, + isNotEmpty, + ); + }); + test('skips running in vm mode if package opts out', () async { final RepositoryPackage package = createFakePackage( 'a_package', @@ -483,6 +594,30 @@ test_on: browser ); }); + test('does not skip running browser in browser mode', () async { + final RepositoryPackage package = createFakePackage( + 'a_package', + packagesDir, + extraFiles: ['test/empty_test.dart'], + ); + package.directory.childFile('dart_test.yaml').writeAsStringSync(''' +test_on: browser +'''); + + final List output = await runCapturingPrint( + runner, ['dart-test', '--platform=browser']); + + expect( + output, + isNot(containsAllInOrder([ + contains('Package has opted out'), + ]))); + expect( + processRunner.recordedCalls, + isNotEmpty, + ); + }); + test('tries to run for a test_on that the tool does not recognize', () async { final RepositoryPackage package = createFakePackage( diff --git a/script/tool/test/federation_safety_check_command_test.dart b/script/tool/test/federation_safety_check_command_test.dart index ac80173cde1..ca4b018a811 100644 --- a/script/tool/test/federation_safety_check_command_test.dart +++ b/script/tool/test/federation_safety_check_command_test.dart @@ -262,6 +262,89 @@ index abc123..def456 100644 ); }); + test('fails with specific text for combo PRs using the recommended tooling', + () async { + final Directory pluginGroupDir = packagesDir.childDirectory('foo'); + final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); + final RepositoryPackage implementation = + createFakePlugin('foo_bar', pluginGroupDir); + final RepositoryPackage platformInterface = + createFakePlugin('foo_platform_interface', pluginGroupDir); + + void addFakeTempPubspecOverrides(RepositoryPackage package) { + final String contents = package.pubspecFile.readAsStringSync(); + package.pubspecFile.writeAsStringSync(''' +$contents + +# FOR TESTING AND INITIAL REVIEW ONLY. $kDoNotLandWarning. +dependency_overrides: + foo_platform_interface: + path: ../../../foo/foo_platform_interface +'''); + } + + addFakeTempPubspecOverrides(appFacing.getExamples().first); + addFakeTempPubspecOverrides(implementation.getExamples().first); + + const String appFacingChanges = ''' +diff --git a/packages/foo/foo/lib/foo.dart b/packages/foo/foo/lib/foo.dart +index abc123..def456 100644 +--- a/packages/foo/foo/lib/foo.dart ++++ b/packages/foo/foo/lib/foo.dart +@@ -51,6 +51,9 @@ Future launchUrl( + return true; + } + ++// This is a new method ++bool foo() => true; ++ + // This in an existing method + void aMethod() { + // Do things. +'''; + + final String changedFileOutput = [ + appFacing.libDirectory.childFile('foo.dart'), + implementation.libDirectory.childFile('foo.dart'), + platformInterface.pubspecFile, + platformInterface.libDirectory.childFile('foo.dart'), + ].map((File file) => file.path).join('\n'); + processRunner.mockProcessesForExecutable['git-diff'] = [ + FakeProcessInfo(MockProcess(stdout: changedFileOutput)), + FakeProcessInfo(MockProcess(stdout: appFacingChanges), + ['', 'HEAD', '--', '/packages/foo/foo/lib/foo.dart']), + // The others diffs don't need to be specified, since empty diff is also + // treated as a non-comment change. + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['federation-safety-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Running for foo/foo...'), + contains('"DO NOT MERGE" found in pubspec.yaml, so this is assumed to ' + 'be the initial combination PR for a federated change, following ' + 'the standard repository procedure. This failure is expected, in ' + 'order to prevent accidentally landing the temporary overrides, ' + 'and will automatically be resolved when the temporary overrides ' + 'are replaced by dependency version bumps later in the process.'), + contains('Running for foo_bar...'), + contains('"DO NOT MERGE" found in pubspec.yaml'), + contains('The following packages had errors:'), + contains('foo/foo:\n' + ' Unresolved combo PR.'), + contains('foo_bar:\n' + ' Unresolved combo PR.'), + ]), + ); + }); + test('ignores test-only changes to interface packages', () async { final Directory pluginGroupDir = packagesDir.childDirectory('foo'); final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); diff --git a/script/tool/test/license_check_command_test.dart b/script/tool/test/license_check_command_test.dart index 09841df74e7..bfd12cac577 100644 --- a/script/tool/test/license_check_command_test.dart +++ b/script/tool/test/license_check_command_test.dart @@ -544,6 +544,23 @@ void main() { contains(' third_party/bad.cc'), ])); }); + + test('passes if Package.swift has license blocks', () async { + final File checked = root.childFile('Package.swift'); + checked.createSync(); + writeLicense(checked, prefix: '// swift-tools-version: 5.9\n'); + + final List output = + await runCapturingPrint(runner, ['license-check']); + + // Sanity check that the test did actually check a file. + expect( + output, + containsAllInOrder([ + contains('Checking Package.swift'), + contains('All files passed validation!'), + ])); + }); }); } diff --git a/script/tool/test/make_deps_path_based_command_test.dart b/script/tool/test/make_deps_path_based_command_test.dart index 09ca9ebc88e..25b0aba2831 100644 --- a/script/tool/test/make_deps_path_based_command_test.dart +++ b/script/tool/test/make_deps_path_based_command_test.dart @@ -122,7 +122,7 @@ ${devDependencies.map((String dep) => ' $dep: $constraint').join('\n')} packageA.pubspecFile.readAsLinesSync(), containsAllInOrder([ '# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.', - '# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins', + '# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins', 'dependency_overrides:', ])); }); diff --git a/script/tool/test/native_test_command_test.dart b/script/tool/test/native_test_command_test.dart index 993ee86d600..cd3569739e2 100644 --- a/script/tool/test/native_test_command_test.dart +++ b/script/tool/test/native_test_command_test.dart @@ -139,6 +139,7 @@ void main() { 'xcrun', [ 'xcodebuild', + 'clean', 'test', '-workspace', '$platform/Runner.xcworkspace', @@ -220,7 +221,7 @@ void main() { getMockXcodebuildListProcess(['RunnerTests', 'RunnerUITests']), // Exit code 66 from testing indicates no tests. FakeProcessInfo( - MockProcess(exitCode: 66), ['xcodebuild', 'test']), + MockProcess(exitCode: 66), ['xcodebuild', 'clean', 'test']), ]; final List output = await runCapturingPrint( runner, ['native-test', '--macos', '--no-unit']); @@ -1469,11 +1470,11 @@ public class FlutterActivityTest { getMockXcodebuildListProcess( ['RunnerTests', 'RunnerUITests']), // iOS list FakeProcessInfo( - MockProcess(), ['xcodebuild', 'test']), // iOS run + MockProcess(), ['xcodebuild', 'clean', 'test']), // iOS run getMockXcodebuildListProcess( ['RunnerTests', 'RunnerUITests']), // macOS list FakeProcessInfo( - MockProcess(), ['xcodebuild', 'test']), // macOS run + MockProcess(), ['xcodebuild', 'clean', 'test']), // macOS run ]; final List output = await runCapturingPrint(runner, [ diff --git a/script/tool/test/podspec_check_command_test.dart b/script/tool/test/podspec_check_command_test.dart index 6745f18b6d4..e416a488fc7 100644 --- a/script/tool/test/podspec_check_command_test.dart +++ b/script/tool/test/podspec_check_command_test.dart @@ -319,8 +319,14 @@ void main() { test('fails if an iOS Swift plugin is missing the search paths workaround', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); + final RepositoryPackage plugin = createFakePlugin( + 'plugin1', + packagesDir, + extraFiles: [ + 'ios/Classes/SomeSwift.swift', + 'ios/plugin1/Package.swift', + ], + ); _writeFakePodspec(plugin, 'ios'); Error? commandError; @@ -378,6 +384,74 @@ void main() { )); }); + test('does not require the search paths workaround for iOS Package.swift', + () async { + final RepositoryPackage plugin = createFakePlugin( + 'plugin1', + packagesDir, + extraFiles: ['ios/plugin1/Package.swift'], + ); + _writeFakePodspec(plugin, 'ios'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('does not require the search paths workaround for Swift tests', + () async { + final RepositoryPackage plugin = createFakePlugin( + 'plugin1', + packagesDir, + extraFiles: [ + 'darwin/Tests/SharedTest.swift', + 'example/ios/RunnerTests/UnitTest.swift', + 'example/ios/RunnerUITests/UITest.swift', + ], + ); + _writeFakePodspec(plugin, 'ios'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test( + 'does not require the search paths workaround for darwin Package.swift', + () async { + final RepositoryPackage plugin = createFakePlugin( + 'plugin1', + packagesDir, + extraFiles: ['darwin/plugin1/Package.swift'], + ); + _writeFakePodspec(plugin, 'darwin'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + test('does not require the search paths workaround for macOS plugins', () async { final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart index 00255f4173c..2cf27027c7c 100644 --- a/script/tool/test/pubspec_check_command_test.dart +++ b/script/tool/test/pubspec_check_command_test.dart @@ -1518,7 +1518,7 @@ ${_topicsSection()} expect( output, containsAllInOrder([ - contains('Dart SDK version for Fluter SDK version 2.0.0 is unknown'), + contains('Dart SDK version for Flutter SDK version 2.0.0 is unknown'), ]), ); }); @@ -1613,7 +1613,7 @@ ${_topicsSection()} contains( ' The following unexpected non-local dependencies were found:\n' ' bad_dependency\n' - ' Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies\n' + ' Please see https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#Dependencies\n' ' for more information and next steps.'), ]), ); @@ -1645,7 +1645,7 @@ ${_topicsSection()} contains( ' The following unexpected non-local dependencies were found:\n' ' bad_dependency\n' - ' Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies\n' + ' Please see https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#Dependencies\n' ' for more information and next steps.'), ]), ); @@ -1729,7 +1729,7 @@ ${_topicsSection()} contains( ' The following unexpected non-local dependencies were found:\n' ' allow_pinned\n' - ' Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies\n' + ' Please see https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#Dependencies\n' ' for more information and next steps.'), ]), ); diff --git a/script/tool/test/readme_check_command_test.dart b/script/tool/test/readme_check_command_test.dart index 12e7a0ac2ff..1cecedd88d0 100644 --- a/script/tool/test/readme_check_command_test.dart +++ b/script/tool/test/readme_check_command_test.dart @@ -119,7 +119,7 @@ a specialized package that includes platform-specific implementation code for Android and/or iOS. For help getting started with Flutter development, view the -[online documentation](https://flutter.dev/docs), which offers tutorials, +[online documentation](https://docs.flutter.dev), which offers tutorials, samples, guidance on mobile development, and a full API reference. '''); @@ -697,7 +697,7 @@ A B C contains('Dart code block at line 3 is not managed by code-excerpt.'), // Ensure that the failure message links to instructions. contains( - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages'), + 'https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md'), contains('Missing code-excerpt management for code block'), ]), ); diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart index 0a30c4dde40..01c71467a6d 100644 --- a/script/tool/test/version_check_command_test.dart +++ b/script/tool/test/version_check_command_test.dart @@ -281,7 +281,7 @@ void main() { contains( ' Breaking changes to platform interfaces are not allowed ' 'without explicit justification.\n' - ' See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' + ' See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md ' 'for more information.'), ])); expect( @@ -876,6 +876,7 @@ packages/plugin/example/android/lint-baseline.xml packages/plugin/example/android/src/androidTest/foo/bar/FooTest.java packages/plugin/example/ios/RunnerTests/Foo.m packages/plugin/example/ios/RunnerUITests/info.plist +packages/plugin/darwin/Tests/Foo.swift packages/plugin/analysis_options.yaml packages/plugin/CHANGELOG.md ''')), diff --git a/script/tool/test/xcode_analyze_command_test.dart b/script/tool/test/xcode_analyze_command_test.dart index 2caf906fc6a..cd4fb7306c4 100644 --- a/script/tool/test/xcode_analyze_command_test.dart +++ b/script/tool/test/xcode_analyze_command_test.dart @@ -106,6 +106,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'ios/Runner.xcworkspace', @@ -146,6 +147,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'ios/Runner.xcworkspace', @@ -244,6 +246,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'macos/Runner.xcworkspace', @@ -278,6 +281,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'macos/Runner.xcworkspace', @@ -350,6 +354,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'ios/Runner.xcworkspace', @@ -366,6 +371,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'macos/Runner.xcworkspace', @@ -406,6 +412,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'macos/Runner.xcworkspace', @@ -445,6 +452,7 @@ void main() { 'xcrun', const [ 'xcodebuild', + 'clean', 'analyze', '-workspace', 'ios/Runner.xcworkspace', diff --git a/third_party/packages/cupertino_icons/CHANGELOG.md b/third_party/packages/cupertino_icons/CHANGELOG.md index 7dea80bf9e3..5d70d6c77ef 100644 --- a/third_party/packages/cupertino_icons/CHANGELOG.md +++ b/third_party/packages/cupertino_icons/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 1.0.8 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/third_party/packages/cupertino_icons/pubspec.yaml b/third_party/packages/cupertino_icons/pubspec.yaml index c4fd06ee5fa..1832255e1ba 100644 --- a/third_party/packages/cupertino_icons/pubspec.yaml +++ b/third_party/packages/cupertino_icons/pubspec.yaml @@ -6,7 +6,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 1.0.8 environment: - sdk: ^3.1.0 + sdk: ^3.2.0 dev_dependencies: flutter: