From 4e796783a265491d1f3d575a3953f5bee043895a Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 8 May 2024 22:17:32 -0400 Subject: [PATCH 01/13] [pointer_interceptor] Remove `implements` from app-facing package (#6699) Dart unit tests started failing for `pointer_interceptor` due to the `implements` tag in the app facing package. This plugin is federated for ios and web, so it looks like the error message is valid. ``` Plugin pointer_interceptor:ios provides an implementation for pointer_interceptor_platform_interface and also references a default implementation for pointer_interceptor_ios, which is currently not supported. Ask the maintainers of pointer_interceptor to either remove the implementation via `implements: pointer_interceptor_platform_interface` or avoid referencing a default implementation via `platforms: ios: default_package: pointer_interceptor_ios`. 00:00 +0 -1: loading /b/s/w/ir/x/w/packages/packages/pointer_interceptor/pointer_interceptor/test/tests_exist_elsewhere_test.dart [E] Error: Please resolve the plugin pubspec errors package:flutter_tools/src/base/common.dart 10:3 throwToolExit package:flutter_tools/src/flutter_plugins.dart 1214:5 resolvePlatformImplementation package:flutter_tools/src/flutter_plugins.dart 1418:55 generateMainDartWithPluginRegistrant ===== asynchronous gap =========================== package:flutter_tools/src/test/test_compiler.dart 163:9 TestCompiler._onCompilationRequest TimeoutException after 0:12:00.000000: Test timed out after 12 minutes. package:test_api/src/backend/invoker.dart 338:28 Invoker._handleError. dart:async/zone.dart 1391:47 _rootRun dart:async/zone.dart 1301:19 _CustomZone.run package:test_api/src/backend/invoker.dart 336:10 Invoker._handleError package:test_api/src/backend/invoker.dart 291:9 Invoker.heartbeat.. dart:async/zone.dart 1399:13 _rootRun dart:async/zone.dart 1301:19 _CustomZone.run package:test_api/src/backend/invoker.dart 290:38 Invoker.heartbeat. dart:async-patch/timer_patch.dart 18:15 Timer._createTimer. dart:isolate-patch/timer_impl.dart 398:19 _Timer._runTimers dart:isolate-patch/timer_impl.dart 429:5 _Timer._handleMessage dart:isolate-patch/isolate_patch.dart 184:12 _RawReceivePort._handleMessage To run this test again: /b/s/w/ir/x/w/flutter/bin/cache/dart-sdk/bin/dart test ``` This removes the tag and does a manual roll. --- .ci/flutter_master.version | 2 +- packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md | 3 ++- packages/pointer_interceptor/pointer_interceptor/pubspec.yaml | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index c4d3ed6b11b3..2f394b7f2afa 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -7920a52dd5680651118312f9be0600797ff2257e +00f40667c16ae3f7cf0c567fe4a43c0a59046951 diff --git a/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md index a822866f9105..cfc10d290ace 100644 --- a/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md @@ -1,8 +1,9 @@ -## NEXT +## 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/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml index 5846d73014f3..e79d06108475 100644 --- a/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor/pubspec.yaml @@ -2,7 +2,7 @@ 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" @@ -10,7 +10,6 @@ environment: flutter: plugin: - implements: pointer_interceptor_platform_interface platforms: web: default_package: pointer_interceptor_web From 4799c496349610388873b8348ac0147a1c4af7f2 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 9 May 2024 09:52:55 -0700 Subject: [PATCH 02/13] [flutter_lints] Rev to 4.0.0; prepare for publishing (#6695) To be submitted after https://github.com/dart-lang/lints/pull/184 is published. --- packages/flutter_lints/CHANGELOG.md | 8 ++++++++ packages/flutter_lints/pubspec.yaml | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/flutter_lints/CHANGELOG.md b/packages/flutter_lints/CHANGELOG.md index 5e5343cc446e..7f1d61d2ff1d 100644 --- a/packages/flutter_lints/CHANGELOG.md +++ b/packages/flutter_lints/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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/pubspec.yaml b/packages/flutter_lints/pubspec.yaml index 051e5cf2a095..680664600f01 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 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: From 6c4482a64492e1cc5980f856bec36fb4d96fa983 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Thu, 9 May 2024 13:06:12 -0500 Subject: [PATCH 03/13] [image_picker_ios] Adds Swift Package Manager compatibility (#6696) Relands https://github.com/flutter/packages/pull/6617. Was reverted in hopes of fixing https://github.com/flutter/flutter/issues/148003, but ended up being fixed by https://github.com/flutter/packages/pull/6694. --- .../image_picker_ios/CHANGELOG.md | 4 +++ .../image_picker_ios/example/ios/Podfile | 2 -- .../ios/Runner.xcodeproj/project.pbxproj | 29 ++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 13 ++++++++ .../xcshareddata/swiftpm/Package.resolved | 13 ++++++++ .../image_picker_ios/ios/Assets/.gitkeep | 0 .../ios/image_picker_ios.podspec | 8 ++--- .../ios/image_picker_ios/Package.swift | 31 +++++++++++++++++++ .../FLTImagePickerImageUtil.h | 0 .../FLTImagePickerImageUtil.m | 0 .../FLTImagePickerMetaDataUtil.h | 0 .../FLTImagePickerMetaDataUtil.m | 0 .../FLTImagePickerPhotoAssetUtil.h | 0 .../FLTImagePickerPhotoAssetUtil.m | 0 .../image_picker_ios}/FLTImagePickerPlugin.m | 0 .../FLTImagePickerPlugin_Test.h | 0 .../FLTPHPickerSaveImageToPathOperation.h | 0 .../FLTPHPickerSaveImageToPathOperation.m | 0 .../Resources/PrivacyInfo.xcprivacy | 0 .../include}/ImagePickerPlugin.modulemap | 0 .../include}/image_picker_ios-umbrella.h | 0 .../image_picker_ios}/FLTImagePickerPlugin.h | 0 .../image_picker_ios/include/module.modulemap | 14 +++++++++ .../Sources/image_picker_ios}/messages.g.h | 0 .../Sources/image_picker_ios}/messages.g.m | 0 .../image_picker_ios/pigeons/messages.dart | 4 +-- .../image_picker_ios/pubspec.yaml | 2 +- 27 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 packages/image_picker/image_picker_ios/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100755 packages/image_picker/image_picker_ios/ios/Assets/.gitkeep create mode 100644 packages/image_picker/image_picker_ios/ios/image_picker_ios/Package.swift rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerImageUtil.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerImageUtil.m (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerMetaDataUtil.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerMetaDataUtil.m (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerPhotoAssetUtil.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerPhotoAssetUtil.m (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerPlugin.m (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTImagePickerPlugin_Test.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTPHPickerSaveImageToPathOperation.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/FLTPHPickerSaveImageToPathOperation.m (100%) rename packages/image_picker/image_picker_ios/ios/{ => image_picker_ios/Sources/image_picker_ios}/Resources/PrivacyInfo.xcprivacy (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios/include}/ImagePickerPlugin.modulemap (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios/include}/image_picker_ios-umbrella.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios/include/image_picker_ios}/FLTImagePickerPlugin.h (100%) create mode 100644 packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/module.modulemap rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/messages.g.h (100%) rename packages/image_picker/image_picker_ios/ios/{Classes => image_picker_ios/Sources/image_picker_ios}/messages.g.m (100%) diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index daff28555b81..259cc601dd22 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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. diff --git a/packages/image_picker/image_picker_ios/example/ios/Podfile b/packages/image_picker/image_picker_ios/example/ios/Podfile index c5bd89706bc7..4ea56c9ad537 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 82e6e009cb0f..8e004beddfa4 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 000000000000..69f8d7acc40b --- /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 000000000000..69f8d7acc40b --- /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/ios/Assets/.gitkeep b/packages/image_picker/image_picker_ios/ios/Assets/.gitkeep deleted file mode 100755 index e69de29bb2d1..000000000000 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 4e4753835202..17ef1b968264 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 000000000000..411869087159 --- /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/ImagePickerPlugin.modulemap"], + resources: [ + .process("Resources") + ], + cSettings: [ + .headerSearchPath("include/image_picker_ios") + ] + ) + ] +) 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/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/FLTImagePickerImageUtil.h 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 100% 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 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/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/FLTImagePickerMetaDataUtil.h 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.h b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/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/FLTImagePickerPhotoAssetUtil.h 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 100% 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 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/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/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/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/FLTPHPickerSaveImageToPathOperation.h 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/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/image_picker_ios/Sources/image_picker_ios/include/module.modulemap b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/module.modulemap new file mode 100644 index 000000000000..ca9c1c4d0322 --- /dev/null +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/include/module.modulemap @@ -0,0 +1,14 @@ +module image_picker_ios { + umbrella header "image_picker_ios-umbrella.h" + + export * + module * { export * } + + explicit module Test { + header "../FLTImagePickerPlugin_Test.h" + header "../FLTImagePickerImageUtil.h" + header "../FLTImagePickerMetaDataUtil.h" + header "../FLTImagePickerPhotoAssetUtil.h" + header "../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/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/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 100% 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 diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index d8ae8954e984..652389dc66fc 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -7,8 +7,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartTestOut: 'test/test_api.g.dart', - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + objcHeaderOut: 'ios/image_picker_ios/Sources/image_picker_ios/messages.g.h', + objcSourceOut: 'ios/image_picker_ios/Sources/image_picker_ios/messages.g.m', objcOptions: ObjcOptions( prefix: 'FLT', ), diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index d57e2b6ee7b8..00c33496cefd 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+1 +version: 0.8.11 environment: sdk: ^3.3.0 From 3fb302d0f31c2877cc48ce6c95126ccf0c48824f Mon Sep 17 00:00:00 2001 From: Victor Ohashi <38299943+VictorOhashi@users.noreply.github.com> Date: Thu, 9 May 2024 18:42:01 -0300 Subject: [PATCH 04/13] [go_router] Feat add route redirect shellroutes (#114559) (#6432) Possible solution for: https://github.com/flutter/flutter/issues/114559 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] 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`.) - [X] I signed the [CLA]. - [X] The title of the PR starts with the name of the package surrounded by square brackets, e.g. `[shared_preferences]` - [X] I [linked to at least one issue that this PR fixes] in the description above. - [X] I updated `pubspec.yaml` with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes]. - [X] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style]. - [ ] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] All existing and new tests are passing. --------- Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com> Co-authored-by: Victor Ohashi --- packages/go_router/CHANGELOG.md | 4 + packages/go_router/lib/src/configuration.dart | 22 ++- packages/go_router/lib/src/route.dart | 127 +++++++++--------- packages/go_router/pubspec.yaml | 2 +- packages/go_router/test/go_router_test.dart | 88 ++++++++++++ 5 files changed, 169 insertions(+), 74 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index c552046d4b61..b1154beb7edb 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 14.1.0 + +- Adds route redirect to ShellRoutes + ## 14.0.2 - Fixes unwanted logs when `hierarchicalLoggingEnabled` was set to `true`. diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart index 09764747aae7..bef882e14605 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); } diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 69dd904f087d..b93520b95aab 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -152,10 +152,67 @@ typedef ExitCallback = FutureOr Function( @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; @@ -209,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'), @@ -325,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: @@ -458,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) { @@ -623,6 +626,7 @@ class ShellRouteContext { class ShellRoute extends ShellRouteBase { /// Constructs a [ShellRoute]. ShellRoute({ + super.redirect, this.builder, this.pageBuilder, this.observers, @@ -783,6 +787,7 @@ class StatefulShellRoute extends ShellRouteBase { /// [navigatorContainerBuilder]. StatefulShellRoute({ required this.branches, + super.redirect, this.builder, this.pageBuilder, required this.navigatorContainerBuilder, @@ -809,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, diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 664fa43d7823..f04e5433ae30 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: 14.0.2 +version: 14.1.0 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index 42bbec0cc4f8..639db182f51d 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', () { From 69a35b2c3aefb13c2ad0f28c3b614be3b80494a6 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 10 May 2024 14:27:04 -0400 Subject: [PATCH 05/13] Roll Flutter from 00f40667c16a to 2bfb1b0e2f61 (9 revisions) (#6706) https://github.com/flutter/flutter/compare/00f40667c16a...2bfb1b0e2f61 2024-05-09 goderbauer@google.com Bump flutter_lints to 4.0 (flutter/flutter#148020) 2024-05-09 104349824+huycozy@users.noreply.github.com Add Badge example (flutter/flutter#148053) 2024-05-09 farquhar.benji@gmail.com Fix ExpandIcon color when ExpansionPanel.canTapOnHeader true (#147097) (flutter/flutter#147098) 2024-05-09 engine-flutter-autoroll@skia.org Roll Flutter Engine from c2e874fdc164 to c0fd3386d018 (1 revision) (flutter/flutter#148040) 2024-05-09 engine-flutter-autoroll@skia.org Roll Flutter Engine from fe08238f5a52 to c2e874fdc164 (1 revision) (flutter/flutter#148038) 2024-05-09 engine-flutter-autoroll@skia.org Roll Flutter Engine from 69f2d9610a18 to fe08238f5a52 (1 revision) (flutter/flutter#148034) 2024-05-09 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#148011) 2024-05-09 engine-flutter-autoroll@skia.org Roll Flutter Engine from bd58d2b2d655 to 69f2d9610a18 (7 revisions) (flutter/flutter#148030) 2024-05-09 kevmoo@users.noreply.github.com [web] Update wasm CLI details to be clear JavaScript is ALSO compiled (flutter/flutter#147944) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC bmparr@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 2f394b7f2afa..985dcc07df4d 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -00f40667c16ae3f7cf0c567fe4a43c0a59046951 +2bfb1b0e2f611173b0712856a42308b4977dc516 From 4d4567a51232115c973beafdf85ec2575d3e718c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Sharma?= <737941+loic-sharma@users.noreply.github.com> Date: Fri, 10 May 2024 13:22:27 -0700 Subject: [PATCH 06/13] [local_auth_darwin] Adds Swift Package Manager compatibility (#6708) Adds Swift Package Manager support to `local_auth_darwin`. This does not migrate the example app's Xcode project to use Swift Package Manager, that's tracked by https://github.com/flutter/flutter/issues/148021. Fixes https://github.com/flutter/flutter/issues/146911 --- .../local_auth/local_auth_darwin/CHANGELOG.md | 4 +++ .../local_auth_darwin/darwin/Assets/.gitkeep | 0 .../darwin/local_auth_darwin.podspec | 7 ++-- .../darwin/local_auth_darwin/Package.swift | 32 +++++++++++++++++++ .../local_auth_darwin}/FLALocalAuthPlugin.m | 4 +-- .../Resources/PrivacyInfo.xcprivacy | 0 .../local_auth_darwin}/FLALocalAuthPlugin.h | 0 .../FLALocalAuthPlugin_Test.h | 0 .../include/local_auth_darwin}/messages.g.h | 0 .../Sources/local_auth_darwin}/messages.g.m | 4 ++- .../local_auth_darwin/pigeons/messages.dart | 7 ++-- .../local_auth/local_auth_darwin/pubspec.yaml | 2 +- 12 files changed, 50 insertions(+), 10 deletions(-) delete mode 100644 packages/local_auth/local_auth_darwin/darwin/Assets/.gitkeep create mode 100644 packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Package.swift rename packages/local_auth/local_auth_darwin/darwin/{Classes => local_auth_darwin/Sources/local_auth_darwin}/FLALocalAuthPlugin.m (98%) rename packages/local_auth/local_auth_darwin/darwin/{ => local_auth_darwin/Sources/local_auth_darwin}/Resources/PrivacyInfo.xcprivacy (100%) rename packages/local_auth/local_auth_darwin/darwin/{Classes => local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin}/FLALocalAuthPlugin.h (100%) rename packages/local_auth/local_auth_darwin/darwin/{Classes => local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin}/FLALocalAuthPlugin_Test.h (100%) rename packages/local_auth/local_auth_darwin/darwin/{Classes => local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin}/messages.g.h (100%) rename packages/local_auth/local_auth_darwin/darwin/{Classes => local_auth_darwin/Sources/local_auth_darwin}/messages.g.m (98%) diff --git a/packages/local_auth/local_auth_darwin/CHANGELOG.md b/packages/local_auth/local_auth_darwin/CHANGELOG.md index 2d209e97bef5..f6df9023d3f5 100644 --- a/packages/local_auth/local_auth_darwin/CHANGELOG.md +++ b/packages/local_auth/local_auth_darwin/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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/darwin/Assets/.gitkeep b/packages/local_auth/local_auth_darwin/darwin/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 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 38dd90cf1057..e3947dd28175 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 000000000000..952abfeac808 --- /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 98% 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 e57adcdff30d..4563686b364e 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,8 +1,8 @@ // Copyright 2013 The Flutter 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 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/Classes/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 similarity index 100% rename from packages/local_auth/local_auth_darwin/darwin/Classes/FLALocalAuthPlugin_Test.h rename to packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/include/local_auth_darwin/FLALocalAuthPlugin_Test.h 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 421b31e94244..6ab8ee5edf95 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/pigeons/messages.dart b/packages/local_auth/local_auth_darwin/pigeons/messages.dart index 15be1f043f31..121e89886516 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 e4482d2eb9bb..2c914c340041 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.0 environment: sdk: ^3.2.3 From a22381ffd5ec27a83a67e9d4c6e8a424d21eedd1 Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Fri, 10 May 2024 13:33:22 -0700 Subject: [PATCH 07/13] [quick_actions_android] Switch to `Compat` version of `ShortcutManager` to support Google surfaces (#6638) Fixes https://github.com/flutter/flutter/issues/147519 --- .../quick_actions_android/CHANGELOG.md | 4 +++ .../plugins/quickactions/QuickActions.java | 31 ++++++++----------- .../quickactions/QuickActionsPlugin.java | 6 ++-- .../quickactions/QuickActionsTest.java | 5 --- .../quick_actions_android/pubspec.yaml | 2 +- 5 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index e8d488aa3a7f..a6987298f760 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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/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 1549cf143b0a..1c124ae3515f 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 fd0184302ab8..eed613347d7a 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; @@ -108,15 +108,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 6c2e963a80c1..7d060d81c14a 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/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index 0fc6f1fe27b6..83065658ba18 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -2,7 +2,7 @@ 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.12 environment: sdk: ^3.2.0 From a8e9147964a17661ec702897abc830ce7240a0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Sharma?= <737941+loic-sharma@users.noreply.github.com> Date: Fri, 10 May 2024 13:36:16 -0700 Subject: [PATCH 08/13] Update .gitignore for Swift Package Manager (#6705) The following directories were created when I added SPM support to the `ios_platform_images` plugin: ``` packages/ios_platform_images/ios/ios_platform_images/.build/ packages/ios_platform_images/ios/ios_platform_images/.swiftpm/ ``` These shouldn't be checked-in to the packages repository. Part of https://github.com/flutter/flutter/issues/148018 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 1c37b779a46c..0af22121e6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,11 @@ pubspec.lock +# iOS and macOS dependencies +.build/ Podfile.lock Pods/ +.swiftpm/ .symlinks/ *instrumentscli*.trace From 1ab2f5b813388daebede1f9905c89c28cfdb9efe Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Fri, 10 May 2024 16:46:24 -0400 Subject: [PATCH 09/13] [camerax] Make fixes required to swap camera_android_camerax for camera_android (#6697) Makes changes needed to land https://github.com/flutter/packages/pull/6629. Specifically: - Fixes timing issue with `stopVideoRecording` such that the `Future` it returns will only complete when CameraX reports that the recording is finalized (via listening for the [finalized video recording event](https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent.Finalize)) - Modifies `startVideoCapturing` such that the `Future` it returns will only complete when CameraX reports that video capturing has started (via listening for the [started video recording event](https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent.Start)) - Adds empty implementation and TODO for implementing `setDescriptionWhileRecording` --- .../camera_android_camerax/CHANGELOG.md | 8 ++ .../camera/camera_android_camerax/README.md | 13 +- .../camerax/GeneratedCameraXLibrary.java | 112 +++++++++++++++++- .../PendingRecordingFlutterApiImpl.java | 12 ++ .../camerax/PendingRecordingHostApiImpl.java | 12 +- .../plugins/camerax/PendingRecordingTest.java | 28 +++++ .../integration_test/integration_test.dart | 78 ++++++++++++ .../lib/src/android_camera_camerax.dart | 48 ++++++-- .../lib/src/camerax_library.g.dart | 82 ++++++++++++- .../lib/src/pending_recording.dart | 12 ++ .../pigeons/camerax_library.dart | 11 ++ .../camera_android_camerax/pubspec.yaml | 2 +- .../test/android_camera_camerax_test.dart | 110 +++++++++++++++-- .../test/test_camerax_library.g.dart | 5 + 14 files changed, 501 insertions(+), 32 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 17c007087abf..cfc8400a712f 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index d7e31e92e03d..d68537ef1607 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -37,6 +37,10 @@ use cases, the plugin behaves according to the following: 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, @@ -64,11 +68,4 @@ For more information on contributing to this plugin, see [`CONTRIBUTING.md`](CON [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 -[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 +[148013]: https://github.com/flutter/flutter/issues/148013 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 41094fd858aa..cbfc36f35cbd 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); } 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 9b4f71080562..b3c46769ad98 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 a1d661d1d9c1..93aa39c56bee 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/PendingRecordingTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java index 92415d5381a0..f25a17ed5d91 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/integration_test/integration_test.dart b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart index 83d20b3585b4..915f522d239e 100644 --- a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart +++ b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart @@ -13,6 +13,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:video_player/video_player.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -178,4 +179,81 @@ void main() { } } }); + + testWidgets('Video capture records valid video', (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(); + + 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/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 5ced7ccccb38..edf24bab37a0 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 @@ -113,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 @@ -122,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 @@ -777,6 +783,15 @@ 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. @@ -963,6 +978,12 @@ class AndroidCameraCameraX extends CameraPlatform { 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; + } } /// Stops the video recording and returns the file where it was saved. @@ -979,23 +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; + await _unbindUseCaseFromLifecycle(videoCapture!); - return XFile(videoOutputPath!); + final XFile videoFile = XFile(videoOutputPath!); + cameraEventStreamController + .add(VideoRecordedEvent(cameraId, videoFile, /* duration */ null)); + return videoFile; } /// Pause the current video recording if it is not null. 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 550854fba3e1..e63a7a6afaf9 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); } 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 971ef49390ac..7dcb19e48c5d 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/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index f51eae8c306a..872c3a622390 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') diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index cabd26b66a2d..9d4191b98b2e 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.6.4+1 +version: 0.6.5 environment: sdk: ^3.1.0 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 23af67c5ca18..aa33254ac14a 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 @@ -1290,7 +1290,7 @@ void main() { 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')) @@ -1312,6 +1312,10 @@ void main() { 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)); // Verify VideoCapture UseCase is bound and camera & its properties @@ -1369,7 +1373,7 @@ void main() { 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')) @@ -1389,6 +1393,10 @@ void main() { 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)); verify(camera.processCameraProvider!.bindToLifecycle( @@ -1448,7 +1456,7 @@ void main() { : 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( @@ -1472,6 +1480,10 @@ void main() { when(mockCamera2CameraInfo.getSupportedHardwareLevel()) .thenAnswer((_) async => CameraMetadata.infoSupportedHardwareLevel3); + // Simulate video recording being started so startVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.start); + await camera.startVideoCapturing(videoCaptureOptions); final CameraImageData mockCameraImageData = MockCameraImageData(); @@ -1516,7 +1528,7 @@ void main() { : 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')) @@ -1529,12 +1541,20 @@ void main() { 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. camera.recording = null; 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; @@ -1542,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; @@ -1550,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; @@ -1601,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); @@ -1642,6 +1674,9 @@ void main() { .thenAnswer((_) async => true); 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); @@ -1663,6 +1698,10 @@ void main() { camera.videoCapture = videoCapture; camera.videoOutputPath = videoOutputPath; + // Simulate video recording being finalized so stopVideoRecording completes. + PendingRecording.videoRecordingEventStreamController + .add(VideoRecordEvent.finalize); + final XFile file = await camera.stopVideoRecording(0); expect(file.path, videoOutputPath); @@ -1691,6 +1730,10 @@ void main() { 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])); @@ -1698,6 +1741,28 @@ void main() { 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( @@ -3695,7 +3760,7 @@ void main() { Future.value(mockCamera2CameraInfo)); const int cameraId = 7; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -3717,6 +3782,10 @@ void main() { 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( @@ -3756,7 +3825,7 @@ void main() { Future.value(mockCamera2CameraInfo)); const int cameraId = 77; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -3778,6 +3847,10 @@ void main() { 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( @@ -3817,7 +3890,7 @@ void main() { Future.value(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')) @@ -3839,6 +3912,10 @@ void main() { 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( @@ -3882,7 +3959,7 @@ void main() { Future.value(mockCamera2CameraInfo)); const int cameraId = 107; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -3906,6 +3983,10 @@ void main() { 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( @@ -3947,7 +4028,7 @@ void main() { Future.value(mockCamera2CameraInfo)); const int cameraId = 97; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -3966,6 +4047,11 @@ void main() { .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( @@ -4009,7 +4095,7 @@ void main() { Future.value(mockCamera2CameraInfo)); const int cameraId = 44; - const String outputPath = '/temp/MOV123.temp'; + const String outputPath = '/temp/REC123.temp'; // Mock method calls. when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) @@ -4033,6 +4119,10 @@ void main() { 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( 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 8a659e460cd7..105447e52689 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); } From f18b89577d6de01de39c3f615eae6fc22ae0a8a8 Mon Sep 17 00:00:00 2001 From: Mairramer <50643541+Mairramer@users.noreply.github.com> Date: Fri, 10 May 2024 17:47:49 -0300 Subject: [PATCH 10/13] [image_picker_android] - will fix crash on Android 12+ devices (#6691) This will fix https://github.com/flutter/flutter/issues/147280 --- .../image_picker_android/CHANGELOG.md | 4 +++ .../imagepicker/ImagePickerDelegate.java | 9 +++-- .../imagepicker/ImagePickerDelegateTest.java | 33 +++++++++++++++---- .../image_picker_android/pubspec.yaml | 2 +- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index b05105f89da7..6a75d6405248 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.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+. 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 96bf727a881a..880c51c5cf73 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(); @@ -692,7 +692,10 @@ private void handleChooseMediaResult(int resultCode, Intent intent) { paths.add(new MediaPath(path, mimeType)); } } else { - paths.add(new MediaPath(fileUtils.getPathFromUri(activity, intent.getData()), null)); + Uri uri = intent.getData(); + if (uri != null) { + paths.add(new MediaPath(fileUtils.getPathFromUri(activity, uri), null)); + } } handleMediaResult(paths); return; 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 81546da52ece..fdee1c9b67e7 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,33 @@ public void onActivityResult_withUnknownRequest_returnsFalse() { assertFalse(isHandled); } + @Test + public void + onActivityResult_whenImagePickedFromGallery_finishesWithEmptyListIfIntentDataIsNull() { + 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); + + @SuppressWarnings("unchecked") + ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); + verify(mockResult).success(pathListCapture.capture()); + assertEquals(0, pathListCapture.getValue().size()); + verifyNoMoreInteractions(mockResult); + } + private ImagePickerDelegate createDelegate() { return new ImagePickerDelegate( mockActivity, diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 41f5c04bde91..ec2767590699 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.11 +version: 0.8.12 environment: sdk: ^3.3.0 From a489d4931be117aef79ff2fb0f3c0687d0f10085 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Fri, 10 May 2024 17:02:58 -0400 Subject: [PATCH 11/13] [Android][webview_flutter] Run integration tests on emulators running Android 34 (#6499) Changes webview_flutter , webview_flutter_android tests to run on emulators running SDK 34 for Android. I also 1. Changed all `unawaited` calls in non-legacy tests to `await`. I believe several of the setup steps need to be completed before running checks based on testing, so this should leave the tests safer overall. 2. Changing the integration test directory structures look like what is described in [the integration_test packages instructions](https://github.com/flutter/flutter/blob/master/packages/integration_test/README.md#package-structure). Without changing the package to this structure, CI seemed to run the tests in parallel. I am still trying to figure out why this is but no longer want to block this PR. Also removes shards for testing packages against Android 33 since no packages are running on emulators running Android 33 anymore. Fixes https://github.com/flutter/flutter/issues/140001. Fixes https://github.com/flutter/flutter/issues/137083. --- .ci.yaml | 83 ------ .ci/targets/android_platform_tests.yaml | 12 +- .../android_platform_tests_api_33_avd.yaml | 29 -- .../webview_flutter_test.dart | 241 ++++++++-------- ....dart => webview_flutter_test_legacy.dart} | 0 .../webview_flutter_test.dart | 258 +++++++++--------- ....dart => webview_flutter_test_legacy.dart} | 0 script/configs/still_requires_api_33_avd.yaml | 2 - 8 files changed, 250 insertions(+), 375 deletions(-) delete mode 100644 .ci/targets/android_platform_tests_api_33_avd.yaml rename packages/webview_flutter/webview_flutter/example/integration_test/{legacy/webview_flutter_test.dart => webview_flutter_test_legacy.dart} (100%) rename packages/webview_flutter/webview_flutter_android/example/integration_test/{legacy/webview_flutter_test.dart => webview_flutter_test_legacy.dart} (100%) delete mode 100644 script/configs/still_requires_api_33_avd.yaml diff --git a/.ci.yaml b/.ci.yaml index 1ed989620b03..3357b2160d3b 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -394,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 @@ -404,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: >- [ @@ -423,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: >- [ @@ -442,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: >- [ @@ -461,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: >- [ @@ -480,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: >- [ @@ -499,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: >- [ @@ -511,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 @@ -663,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 diff --git a/.ci/targets/android_platform_tests.yaml b/.ci/targets/android_platform_tests.yaml index 23fc6039bdf3..d1019f7df182 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 cc5324b141fe..000000000000 --- 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/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 a18c1be03456..de5d3df6dea7 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( @@ -395,14 +404,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 +429,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 +463,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 +514,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 +572,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 +601,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 +621,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 +642,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. @@ -665,8 +674,8 @@ 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) async { NavigationDecision decision = NavigationDecision.prevent; @@ -674,11 +683,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 +703,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 +734,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 +761,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 +780,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 +804,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 +823,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 +853,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_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index cee7d65f33fe..4c22df1ff28c 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/script/configs/still_requires_api_33_avd.yaml b/script/configs/still_requires_api_33_avd.yaml deleted file mode 100644 index 7368d3d16e0c..000000000000 --- 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 From 75930f87f85f5dce7b885178fbf4f6f5c2415671 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 10 May 2024 17:14:01 -0400 Subject: [PATCH 12/13] [url_launcher] Add Swift Package Manager support (#6677) Adds SPM support for both iOS and macOS. Also opportunistically updates the unit tests for macOS to match the changes made in https://github.com/flutter/packages/pull/4959 since I noticed that they were failing when running locally. This will prevent issues when we update CI in the future. Fixes https://github.com/flutter/flutter/issues/146916 --- .../url_launcher_ios/CHANGELOG.md | 4 +++ .../ios/url_launcher_ios.podspec | 4 +-- .../ios/url_launcher_ios/Package.swift | 28 +++++++++++++++++++ .../Sources/url_launcher_ios}/Launcher.swift | 0 .../Resources/PrivacyInfo.xcprivacy | 0 .../url_launcher_ios}/URLLaunchSession.swift | 0 .../url_launcher_ios}/URLLauncherPlugin.swift | 0 .../url_launcher_ios}/messages.g.swift | 0 .../url_launcher_ios/pigeons/messages.dart | 2 +- .../url_launcher_ios/pubspec.yaml | 2 +- .../url_launcher_macos/CHANGELOG.md | 3 +- .../macos/RunnerTests/RunnerTests.swift | 18 ++++++++++-- .../macos/url_launcher_macos.podspec | 3 +- .../macos/url_launcher_macos/Package.swift | 28 +++++++++++++++++++ .../url_launcher_macos/Resources/.gitkeep | 0 .../UrlLauncherPlugin.swift | 0 .../url_launcher_macos}/messages.g.swift | 0 .../url_launcher_macos/pigeons/messages.dart | 3 +- .../url_launcher_macos/pubspec.yaml | 2 +- 19 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 packages/url_launcher/url_launcher_ios/ios/url_launcher_ios/Package.swift rename packages/url_launcher/url_launcher_ios/ios/{Classes => url_launcher_ios/Sources/url_launcher_ios}/Launcher.swift (100%) rename packages/url_launcher/url_launcher_ios/ios/{ => url_launcher_ios/Sources/url_launcher_ios}/Resources/PrivacyInfo.xcprivacy (100%) rename packages/url_launcher/url_launcher_ios/ios/{Classes => url_launcher_ios/Sources/url_launcher_ios}/URLLaunchSession.swift (100%) rename packages/url_launcher/url_launcher_ios/ios/{Classes => url_launcher_ios/Sources/url_launcher_ios}/URLLauncherPlugin.swift (100%) rename packages/url_launcher/url_launcher_ios/ios/{Classes => url_launcher_ios/Sources/url_launcher_ios}/messages.g.swift (100%) create mode 100644 packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Package.swift create mode 100644 packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/Resources/.gitkeep rename packages/url_launcher/url_launcher_macos/macos/{Classes => url_launcher_macos/Sources/url_launcher_macos}/UrlLauncherPlugin.swift (100%) rename packages/url_launcher/url_launcher_macos/macos/{Classes => url_launcher_macos/Sources/url_launcher_macos}/messages.g.swift (100%) diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index b5bb9c81481b..0c6d13e0941e 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/ios/url_launcher_ios.podspec b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec index 9dcf68c01f0d..fa30175cb6bf 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 000000000000..5dbdd8ad4d7e --- /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 f5dc1052b320..c7097b41a74a 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 fd8c54619c55..65e9c2048351 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_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index df9447ad852f..44b282ca29e0 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 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/example/macos/RunnerTests/RunnerTests.swift b/packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/RunnerTests.swift index 622cffb3404a..e58397e1e77d 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/macos/url_launcher_macos.podspec b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos.podspec index 70864ec4f36a..de18c66e7d0d 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 000000000000..13ddb82f9416 --- /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/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/Resources/.gitkeep b/packages/url_launcher/url_launcher_macos/macos/url_launcher_macos/Sources/url_launcher_macos/Resources/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 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 fd0027e66787..bd97c681d8e0 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 0a84ea922389..4152e5f7c10e 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_macos description: macOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.1.0 +version: 3.2.0 environment: sdk: ^3.1.0 From f1ca81a592982661c137a7a4495d275718bbf1e3 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 10 May 2024 17:24:34 -0400 Subject: [PATCH 13/13] [quick_actions] Add Swift Package Manager support (#6682) Adds SPM support. Does not include any package changes, to maintain compatibility with `stable`. Also adds a missing `import` in a test; it looks like it was probabl/y compiling before due to bleed-through from the Cocoapod dependency, and switching to SPM broke it. The test references a UIKit type, so should definitely have been importing it. Fixes https://github.com/flutter/flutter/issues/146915 --- .../quick_actions_ios/CHANGELOG.md | 3 ++- .../Mocks/MockShortcutItemProvider.swift | 2 ++ .../ios/quick_actions_ios.podspec | 4 +-- .../ios/quick_actions_ios/Package.swift | 27 +++++++++++++++++++ .../QuickActionsPlugin.swift | 0 .../Resources/PrivacyInfo.xcprivacy | 0 .../ShortcutItemProviding.swift | 0 .../quick_actions_ios}/messages.g.swift | 0 .../quick_actions_ios/pigeons/messages.dart | 2 +- .../quick_actions_ios/pubspec.yaml | 2 +- 10 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 packages/quick_actions/quick_actions_ios/ios/quick_actions_ios/Package.swift rename packages/quick_actions/quick_actions_ios/ios/{Classes => quick_actions_ios/Sources/quick_actions_ios}/QuickActionsPlugin.swift (100%) rename packages/quick_actions/quick_actions_ios/ios/{ => quick_actions_ios/Sources/quick_actions_ios}/Resources/PrivacyInfo.xcprivacy (100%) rename packages/quick_actions/quick_actions_ios/ios/{Classes => quick_actions_ios/Sources/quick_actions_ios}/ShortcutItemProviding.swift (100%) rename packages/quick_actions/quick_actions_ios/ios/{Classes => quick_actions_ios/Sources/quick_actions_ios}/messages.g.swift (100%) diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md index c7f6a3b983c8..47d7d1ddbf78 100644 --- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 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/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift index 85477415667e..c1924113ba11 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/ios/quick_actions_ios.podspec b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec index 77082f66ec3b..6bfb72f42318 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 000000000000..c02db8e9c262 --- /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 100% 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 diff --git a/packages/quick_actions/quick_actions_ios/pigeons/messages.dart b/packages/quick_actions/quick_actions_ios/pigeons/messages.dart index 553d8552cf7f..7cf7d20d587f 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 3a7cd8f8f16d..41bfb0ceebc2 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.0 environment: sdk: ^3.2.3