From eb0e98ba3cfc6299115a0ac4bc1db727f6d1cb54 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Thu, 22 Dec 2022 15:35:05 +0100 Subject: [PATCH 01/11] [DOC][Android] - Add Android theme compatibility documentation --- packages/local_auth/local_auth/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index a68692ea940a..554a73622158 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -252,6 +252,24 @@ types (such as face scanning) and you want to support SDKs lower than Q, _do not_ call `getAvailableBiometrics`. Simply call `authenticate` with `biometricOnly: true`. This will return an error if there was no hardware available. +Furthermore, you need to update the `AndroidManifest.xml` file with the +`Theme.AppCompat.Light` theme to be compatible with **Android version 8 and below**; +otherwise the app crashes for those versions. + +```xml +... + + + +... +``` + ## Sticky Auth You can set the `stickyAuth` option on the plugin to true so that plugin does not From a39808b7758bbea577ebd46c7aa5223d7a8d5963 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Thu, 22 Dec 2022 16:00:42 +0100 Subject: [PATCH 02/11] [VERSION] - Upgrade version to 2.1.4 --- packages/local_auth/local_auth/CHANGELOG.md | 4 ++++ packages/local_auth/local_auth/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index 21fed07d895b..5d340bb7399c 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.4 + +* Updates documentation for Android version 8 and below theme compatibility. + ## 2.1.3 * Updates minimum Flutter version to 2.10. diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index 722c993edb50..fb6668009ca5 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 2.1.3 +version: 2.1.4 environment: sdk: ">=2.14.0 <3.0.0" From 144d4b196185e3ec9354d81b22dd3756e1517fd5 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Fri, 6 Jan 2023 13:59:12 +0100 Subject: [PATCH 03/11] [local_auth] Suggest set up Android LaunchTheme theme in styles.xml --- packages/local_auth/local_auth/README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index 554a73622158..f758939415a9 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -252,9 +252,22 @@ types (such as face scanning) and you want to support SDKs lower than Q, _do not_ call `getAvailableBiometrics`. Simply call `authenticate` with `biometricOnly: true`. This will return an error if there was no hardware available. -Furthermore, you need to update the `AndroidManifest.xml` file with the -`Theme.AppCompat.Light` theme to be compatible with **Android version 8 and below**; -otherwise the app crashes for those versions. +#### Android theme + +On Android you need to update the `LaunchTheme` parent style with the `Theme.AppCompat.Light` theme to be compatible with **Android version 8 and below**; otherwise the app crashes for those versions. To do that go to `android > app > src > main > res > values > styles.xml` and look for the style with name `LaunchTheme` (Notice that `LaunchTheme` must be referenced in the `AndroidManifest.xml` file). Then, change the parent style with the new value and you should have set up something like this: + +```xml +... + + + ... + +... +``` + +In case you don't have a `styles.xml` file for your Android project you can set up the Android theme directly on your `android > app > src > main > AndroidManifest.xml` file: ```xml ... From 043b928ae521b48078e6971bfe842938bf807cba Mon Sep 17 00:00:00 2001 From: Abel1027 <64153497+Abel1027@users.noreply.github.com> Date: Fri, 13 Jan 2023 10:24:14 -0300 Subject: [PATCH 04/11] [DOC][local_auth] Update Android compatibility suggestion Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> --- packages/local_auth/local_auth/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index f758939415a9..88226a07e252 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -254,7 +254,7 @@ This will return an error if there was no hardware available. #### Android theme -On Android you need to update the `LaunchTheme` parent style with the `Theme.AppCompat.Light` theme to be compatible with **Android version 8 and below**; otherwise the app crashes for those versions. To do that go to `android > app > src > main > res > values > styles.xml` and look for the style with name `LaunchTheme` (Notice that `LaunchTheme` must be referenced in the `AndroidManifest.xml` file). Then, change the parent style with the new value and you should have set up something like this: +On Android, ensure that the `LaunchTheme` of your app has a parent style with the `Theme.AppCompat.Light` theme; otherwise the app may crash. To do that go to `android > app > src > main > res > values > styles.xml` and look for the style with name `LaunchTheme` (Notice that `LaunchTheme` must be referenced in the `AndroidManifest.xml` file). Then, change the parent style with the new value and you should have set up something like this: ```xml ... From 1b1ab97c87bf266b7660d99ca873e812981e6f69 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Sat, 4 Feb 2023 12:39:06 +0100 Subject: [PATCH 05/11] [DOC][local_auth] Update android theme suggestion with DayNight option --- packages/local_auth/local_auth/README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index f758939415a9..96891896122c 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -244,7 +244,7 @@ Update your project's `AndroidManifest.xml` file to include the ``` -### Compatibility +### Compatibilitypopup On Android, you can check only for existence of fingerprint hardware prior to API 29 (Android Q). Therefore, if you would like to support other biometrics @@ -254,12 +254,19 @@ This will return an error if there was no hardware available. #### Android theme -On Android you need to update the `LaunchTheme` parent style with the `Theme.AppCompat.Light` theme to be compatible with **Android version 8 and below**; otherwise the app crashes for those versions. To do that go to `android > app > src > main > res > values > styles.xml` and look for the style with name `LaunchTheme` (Notice that `LaunchTheme` must be referenced in the `AndroidManifest.xml` file). Then, change the parent style with the new value and you should have set up something like this: +You need to update the `LaunchTheme` parent style with a valid `Theme.AppCompat` +theme to be compatible with **Android version 8 and below**, otherwise the app +crashes for those versions. For example, use `Theme.AppCompat.DayNight` to +enable light/dark modes for the biometric dialog. To do that go to +`android/app/src/main/res/values/styles.xml` and look for the style with name +`LaunchTheme` (Notice that `LaunchTheme` must be referenced in the +`AndroidManifest.xml` file to apply the changes made in `styles.xml`). +Then change the parent for that style as follows: ```xml ... - ... @@ -267,7 +274,7 @@ On Android you need to update the `LaunchTheme` parent style with the `Theme.App ... ``` -In case you don't have a `styles.xml` file for your Android project you can set up the Android theme directly on your `android > app > src > main > AndroidManifest.xml` file: +If you don't have a `styles.xml` file for your Android project you can set up the Android theme directly in `android/app/src/main/AndroidManifest.xml`: ```xml ... @@ -275,7 +282,7 @@ In case you don't have a `styles.xml` file for your Android project you can set ... From 890e7c748b438596bdf50d6181c925d7cbca6647 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Sat, 4 Feb 2023 12:48:26 +0100 Subject: [PATCH 06/11] [DOC][local_auth] Remove typo --- packages/local_auth/local_auth/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index 96891896122c..0c2a5842df29 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -244,7 +244,7 @@ Update your project's `AndroidManifest.xml` file to include the ``` -### Compatibilitypopup +### Compatibility On Android, you can check only for existence of fingerprint hardware prior to API 29 (Android Q). Therefore, if you would like to support other biometrics From 51bfbca7a3339ddaac33eeb0a2d7285709b6e335 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Sat, 4 Feb 2023 13:39:29 +0100 Subject: [PATCH 07/11] Merge origin changes --- .ci.yaml | 239 +++- .ci/flutter_master.version | 2 +- .ci/flutter_stable.version | 2 +- .ci/scripts/build_all_plugins.sh | 3 +- .ci/scripts/create_all_plugins_app.sh | 2 +- .ci/targets/ios_build_all_plugins.yaml | 11 + ...orm_tests.yaml => ios_platform_tests.yaml} | 0 ...gins.yaml => macos_build_all_plugins.yaml} | 4 +- ...podspecs.yaml => macos_lint_podspecs.yaml} | 4 +- .ci/targets/macos_platform_tests.yaml | 19 + ...ns.yaml => windows_build_all_plugins.yaml} | 4 +- .cirrus.yml | 101 +- .github/workflows/release.yml | 2 +- .github/workflows/scorecards-analysis.yml | 4 +- CODEOWNERS | 13 +- README.md | 2 +- analysis_options.yaml | 58 +- packages/camera/camera/CHANGELOG.md | 14 +- packages/camera/camera/example/lib/main.dart | 6 +- .../camera/lib/src/camera_controller.dart | 185 ++- packages/camera/camera/pubspec.yaml | 10 +- .../camera/test/camera_image_stream_test.dart | 67 +- .../camera/test/camera_preview_test.dart | 20 +- packages/camera/camera/test/camera_test.dart | 33 +- packages/camera/camera_android/CHANGELOG.md | 22 + .../camera_android/android/build.gradle | 2 +- .../io/flutter/plugins/camera/Camera.java | 7 +- .../plugins/camera/CameraProperties.java | 38 +- .../io/flutter/plugins/camera/CameraZoom.java | 52 - .../resolution/ResolutionFeature.java | 33 +- .../features/zoomlevel/ZoomLevelFeature.java | 50 +- .../camera/features/zoomlevel/ZoomUtils.java | 11 +- .../camera/media/MediaRecorderBuilder.java | 2 +- .../camera/CameraPropertiesImplTest.java | 24 + .../plugins/camera/CameraZoomTest.java | 125 -- .../resolution/ResolutionFeatureTest.java | 98 ++ .../zoomlevel/ZoomLevelFeatureTest.java | 55 +- .../features/zoomlevel/ZoomUtilsTest.java | 37 +- .../example/lib/camera_controller.dart | 149 ++- .../camera_android/example/lib/main.dart | 7 +- .../camera_android/example/pubspec.yaml | 2 +- .../lib/src/android_camera.dart | 60 +- .../camera/camera_android/lib/src/utils.dart | 9 +- packages/camera/camera_android/pubspec.yaml | 4 +- .../test/android_camera_test.dart | 34 +- .../camera_android_camerax/CHANGELOG.md | 4 + .../android/build.gradle | 4 +- .../android/src/main/AndroidManifest.xml | 5 + .../camerax/CameraAndroidCameraxPlugin.java | 28 +- .../plugins/camerax/CameraFlutterApiImpl.java | 22 + .../camerax/CameraInfoHostApiImpl.java | 4 +- .../camerax/CameraPermissionsManager.java | 120 ++ .../camerax/CameraSelectorHostApiImpl.java | 11 +- .../flutter/plugins/camerax/CameraXProxy.java | 13 + .../camerax/DeviceOrientationManager.java | 329 +++++ .../camerax/GeneratedCameraXLibrary.java | 400 ++++++ .../plugins/camerax/InstanceManager.java | 7 +- .../ProcessCameraProviderHostApiImpl.java | 83 +- .../camerax/SystemServicesFlutterApiImpl.java | 22 + .../camerax/SystemServicesHostApiImpl.java | 112 ++ .../camerax/CameraPermissionsManagerTest.java | 89 ++ .../flutter/plugins/camerax/CameraTest.java | 52 + .../camerax/DeviceOrientationManagerTest.java | 313 +++++ .../plugins/camerax/InstanceManagerTest.java | 15 + .../camerax/ProcessCameraProviderTest.java | 56 + .../plugins/camerax/SystemServicesTest.java | 137 ++ .../example/android/build.gradle | 2 +- ...roid_camera_camerax_flutter_api_impls.dart | 17 +- .../lib/src/camera.dart | 53 + .../lib/src/camera_info.dart | 2 +- .../lib/src/camera_selector.dart | 2 +- ...ary.pigeon.dart => camerax_library.g.dart} | 286 +++++ .../lib/src/instance_manager.dart | 20 +- .../lib/src/java_object.dart | 2 +- .../lib/src/process_camera_provider.dart | 87 +- .../lib/src/system_services.dart | 137 ++ .../lib/src/use_case.dart | 14 + .../pigeons/camerax_library.dart | 42 +- .../camera_android_camerax/pubspec.yaml | 1 + .../test/camera_info_test.dart | 4 +- .../test/camera_info_test.mocks.dart | 12 +- .../test/camera_selector_test.dart | 4 +- .../test/camera_selector_test.mocks.dart | 38 +- .../test/camera_test.dart | 26 + .../test/process_camera_provider_test.dart | 113 +- .../process_camera_provider_test.mocks.dart | 62 +- .../test/system_services_test.dart | 101 ++ .../test/system_services_test.mocks.dart | 66 + ...igeon.dart => test_camerax_library.g.dart} | 173 ++- .../camera/camera_avfoundation/CHANGELOG.md | 13 + .../example/lib/camera_controller.dart | 153 ++- .../camera_avfoundation/example/lib/main.dart | 7 +- .../camera_avfoundation/example/pubspec.yaml | 2 +- .../lib/src/avfoundation_camera.dart | 60 +- .../camera_avfoundation/lib/src/utils.dart | 9 +- .../camera/camera_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_camera_test.dart | 37 +- .../camera_platform_interface/CHANGELOG.md | 12 + .../method_channel/method_channel_camera.dart | 46 +- .../lib/src/types/exposure_mode.dart | 2 - .../lib/src/types/focus_mode.dart | 2 - .../lib/src/types/image_format_group.dart | 1 - .../lib/src/utils/utils.dart | 2 - .../camera_platform_interface/pubspec.yaml | 4 +- .../method_channel_camera_test.dart | 10 +- packages/camera/camera_web/CHANGELOG.md | 8 + .../example/integration_test/camera_test.dart | 19 +- .../camera/camera_web/example/pubspec.yaml | 2 +- .../camera_web/lib/src/camera_service.dart | 8 +- packages/camera/camera_web/pubspec.yaml | 4 +- packages/camera/camera_windows/CHANGELOG.md | 8 + .../camera_windows/example/pubspec.yaml | 2 +- .../camera_windows/lib/camera_windows.dart | 15 +- packages/camera/camera_windows/pubspec.yaml | 4 +- .../test/camera_windows_test.dart | 10 +- packages/espresso/CHANGELOG.md | 9 + packages/espresso/android/build.gradle | 6 +- packages/espresso/example/pubspec.yaml | 2 +- packages/espresso/pubspec.yaml | 4 +- .../file_selector/file_selector/CHANGELOG.md | 5 + .../example/lib/get_directory_page.dart | 10 +- .../example/lib/open_image_page.dart | 10 +- .../lib/open_multiple_images_page.dart | 10 +- .../example/lib/open_text_page.dart | 10 +- .../file_selector/example/pubspec.yaml | 1 + .../file_selector/file_selector/pubspec.yaml | 2 +- .../file_selector_ios/CHANGELOG.md | 5 + .../example/lib/open_image_page.dart | 10 +- .../lib/open_multiple_images_page.dart | 10 +- .../example/lib/open_text_page.dart | 10 +- .../file_selector_ios/example/pubspec.yaml | 3 +- .../file_selector_ios/pigeons/messages.dart | 2 +- .../file_selector_ios/pubspec.yaml | 2 +- .../test/file_selector_ios_test.dart | 2 +- .../test/file_selector_ios_test.mocks.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../file_selector_linux/CHANGELOG.md | 9 + .../example/lib/get_directory_page.dart | 10 +- .../lib/get_multiple_directories_page.dart | 87 ++ .../example/lib/home_page.dart | 7 + .../file_selector_linux/example/lib/main.dart | 3 + .../example/lib/open_image_page.dart | 10 +- .../lib/open_multiple_images_page.dart | 10 +- .../example/lib/open_text_page.dart | 10 +- .../file_selector_linux/example/pubspec.yaml | 5 +- .../lib/file_selector_linux.dart | 27 +- .../file_selector_linux/linux/.gitignore | 2 + .../linux/file_selector_plugin.cc | 6 +- .../linux/test/file_selector_plugin_test.cc | 14 + .../file_selector_linux/pubspec.yaml | 6 +- .../test/file_selector_linux_test.dart | 367 +++--- .../file_selector_macos/CHANGELOG.md | 9 + .../example/lib/get_directory_page.dart | 10 +- .../example/lib/open_image_page.dart | 10 +- .../lib/open_multiple_images_page.dart | 10 +- .../example/lib/open_text_page.dart | 10 +- .../macos/RunnerTests/RunnerTests.swift | 120 +- .../file_selector_macos/example/pubspec.yaml | 2 +- .../lib/file_selector_macos.dart | 105 +- .../lib/src/messages.g.dart | 227 ++++ .../macos/Classes/FileSelectorPlugin.swift | 113 +- .../macos/Classes/messages.g.swift | 228 ++++ .../pigeons/copyright.txt | 0 .../file_selector_macos/pigeons/messages.dart | 84 ++ .../file_selector_macos/pubspec.yaml | 7 +- .../test/file_selector_macos_test.dart | 335 ++--- .../test/file_selector_macos_test.mocks.dart | 51 + .../test/messages_test.g.dart | 107 ++ .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../file_selector_web/CHANGELOG.md | 4 + .../file_selector_web/example/pubspec.yaml | 2 +- .../file_selector_web/pubspec.yaml | 2 +- .../file_selector_windows/CHANGELOG.md | 5 + .../example/lib/get_directory_page.dart | 10 +- .../example/lib/open_image_page.dart | 10 +- .../lib/open_multiple_images_page.dart | 10 +- .../example/lib/open_text_page.dart | 10 +- .../example/pubspec.yaml | 2 +- .../pigeons/messages.dart | 2 +- .../file_selector_windows/pubspec.yaml | 2 +- .../test/file_selector_windows_test.dart | 2 +- .../file_selector_windows_test.mocks.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../CHANGELOG.md | 2 +- .../example/pubspec.yaml | 1 + .../pubspec.yaml | 2 +- .../google_maps_flutter/CHANGELOG.md | 8 + .../google_maps_flutter/README.md | 1 + .../example/lib/readme_sample.dart | 2 +- .../google_maps_flutter/example/pubspec.yaml | 2 +- .../google_maps_flutter/pubspec.yaml | 4 +- .../test/fake_maps_controllers.dart | 15 +- .../test/map_creation_test.dart | 4 + .../google_maps_flutter_android/CHANGELOG.md | 16 + .../android/build.gradle | 2 +- .../example/pubspec.yaml | 2 +- .../lib/src/google_maps_flutter_android.dart | 58 +- .../google_maps_flutter_android/pubspec.yaml | 2 +- .../google_maps_flutter_android_test.dart | 9 +- .../google_maps_flutter_ios/CHANGELOG.md | 5 + .../example/pubspec.yaml | 2 +- .../lib/src/google_maps_flutter_ios.dart | 56 +- .../google_maps_flutter_ios/pubspec.yaml | 4 +- .../test/google_maps_flutter_ios_test.dart | 9 +- .../CHANGELOG.md | 8 + .../method_channel_google_maps_flutter.dart | 56 +- .../pubspec.yaml | 4 +- ...thod_channel_google_maps_flutter_test.dart | 9 +- .../google_maps_flutter_web/CHANGELOG.md | 9 + .../example/pubspec.yaml | 2 +- .../lib/src/convert.dart | 48 +- .../google_maps_flutter_web/pubspec.yaml | 4 +- .../google_sign_in/CHANGELOG.md | 9 + .../google_sign_in/google_sign_in/README.md | 16 +- .../google_sign_in/example/lib/main.dart | 8 +- .../google_sign_in/example/pubspec.yaml | 2 +- .../google_sign_in/lib/google_sign_in.dart | 5 +- .../google_sign_in/pubspec.yaml | 5 +- .../google_sign_in_android/CHANGELOG.md | 8 + .../android/build.gradle | 4 +- .../example/pubspec.yaml | 2 +- .../google_sign_in_android/pubspec.yaml | 4 +- .../test/google_sign_in_android_test.dart | 4 +- .../google_sign_in_ios/CHANGELOG.md | 4 + .../google_sign_in_ios/example/pubspec.yaml | 2 +- .../google_sign_in_ios/pubspec.yaml | 2 +- .../test/google_sign_in_ios_test.dart | 4 +- .../CHANGELOG.md | 2 +- .../pubspec.yaml | 2 +- .../method_channel_google_sign_in_test.dart | 4 +- .../google_sign_in_web/CHANGELOG.md | 5 + .../integration_test/gapi_utils_test.dart | 2 +- .../google_sign_in_web/example/pubspec.yaml | 2 +- .../lib/google_sign_in_web.dart | 2 +- .../src/{generated => js_interop}/gapi.dart | 16 +- .../{generated => js_interop}/gapiauth2.dart | 108 +- .../google_sign_in_web/lib/src/load_gapi.dart | 2 +- .../google_sign_in_web/lib/src/utils.dart | 2 +- .../google_sign_in_web/pubspec.yaml | 4 +- .../image_picker/image_picker/CHANGELOG.md | 8 + .../image_picker/example/lib/main.dart | 2 +- .../image_picker/example/pubspec.yaml | 2 +- .../image_picker/image_picker/pubspec.yaml | 4 +- .../image_picker_android/CHANGELOG.md | 8 + .../example/lib/main.dart | 2 +- .../image_picker_android/example/pubspec.yaml | 2 +- .../image_picker_android/pubspec.yaml | 4 +- .../image_picker_for_web/CHANGELOG.md | 4 + .../image_picker_for_web/example/pubspec.yaml | 2 +- .../image_picker_for_web/pubspec.yaml | 2 +- .../image_picker_ios/CHANGELOG.md | 16 + .../xcshareddata/xcschemes/Runner.xcscheme | 10 - .../ios/RunnerTests/ImagePickerPluginTests.m | 54 +- .../PickerSaveImageToPathOperationTests.m | 1 - .../ImagePickerFromGalleryUITests.m | 88 +- .../ImagePickerFromLimitedGalleryUITests.m | 81 +- .../image_picker_ios/example/pubspec.yaml | 2 +- .../ios/Classes/FLTImagePickerImageUtil.m | 2 +- .../ios/Classes/FLTImagePickerPlugin.m | 6 +- .../FLTPHPickerSaveImageToPathOperation.m | 6 +- .../lib/image_picker_ios.dart | 18 +- .../image_picker_ios/pigeons/messages.dart | 2 +- .../image_picker_ios/pubspec.yaml | 4 +- .../test/image_picker_ios_test.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../image_picker_windows/CHANGELOG.md | 5 + .../example/lib/main.dart | 32 +- .../image_picker_windows/example/pubspec.yaml | 2 +- .../image_picker_windows/pubspec.yaml | 4 +- .../test/image_picker_windows_test.dart | 56 +- .../in_app_purchase/CHANGELOG.md | 13 + .../example/android/app/build.gradle | 2 +- .../in_app_purchase/example/lib/main.dart | 26 +- .../in_app_purchase/example/pubspec.yaml | 2 +- .../in_app_purchase/pubspec.yaml | 10 +- .../in_app_purchase_android/CHANGELOG.md | 18 + .../android/build.gradle | 6 +- .../example/lib/main.dart | 2 + .../example/pubspec.yaml | 2 +- .../billing_client_wrapper.dart | 8 +- .../in_app_purchase_android/pubspec.yaml | 4 +- ...in_app_purchase_android_platform_test.dart | 12 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../in_app_purchase_storekit/CHANGELOG.md | 19 +- .../Classes/FIAObjectTranslator.h | 0 .../Classes/FIAObjectTranslator.m | 0 .../Classes/FIAPPaymentQueueDelegate.h | 0 .../Classes/FIAPPaymentQueueDelegate.m | 0 .../Classes/FIAPReceiptManager.h | 0 .../Classes/FIAPReceiptManager.m | 3 + .../Classes/FIAPRequestHandler.h | 0 .../Classes/FIAPRequestHandler.m | 0 .../Classes/FIAPaymentQueueHandler.h | 0 .../Classes/FIAPaymentQueueHandler.m | 0 .../Classes/FIATransactionCache.h | 0 .../Classes/FIATransactionCache.m | 0 .../Classes/InAppPurchasePlugin.h | 0 .../Classes/InAppPurchasePlugin.m | 0 .../in_app_purchase_storekit.podspec | 0 .../example/lib/main.dart | 2 + .../example/pubspec.yaml | 2 +- .../RunnerTests/InAppPurchasePluginTests.m | 17 + .../ios/Assets/.gitkeep | 1 - .../ios/Classes/FIAObjectTranslator.h | 2 +- .../ios/Classes/FIAObjectTranslator.m | 2 +- .../ios/Classes/FIAPPaymentQueueDelegate.h | 2 +- .../ios/Classes/FIAPPaymentQueueDelegate.m | 2 +- .../ios/Classes/FIAPReceiptManager.h | 2 +- .../ios/Classes/FIAPReceiptManager.m | 2 +- .../ios/Classes/FIAPRequestHandler.h | 2 +- .../ios/Classes/FIAPRequestHandler.m | 2 +- .../ios/Classes/FIAPaymentQueueHandler.h | 2 +- .../ios/Classes/FIAPaymentQueueHandler.m | 2 +- .../ios/Classes/FIATransactionCache.h | 2 +- .../ios/Classes/FIATransactionCache.m | 2 +- .../ios/Classes/InAppPurchasePlugin.h | 2 +- .../ios/Classes/InAppPurchasePlugin.m | 2 +- .../ios/in_app_purchase_storekit.podspec | 2 +- .../sk_payment_queue_wrapper.dart | 12 +- .../macos/Assets/.gitkeep | 1 - .../macos/Classes/FIAObjectTranslator.h | 2 +- .../macos/Classes/FIAObjectTranslator.m | 2 +- .../macos/Classes/FIAPPaymentQueueDelegate.h | 2 +- .../macos/Classes/FIAPPaymentQueueDelegate.m | 2 +- .../macos/Classes/FIAPReceiptManager.h | 2 +- .../macos/Classes/FIAPReceiptManager.m | 2 +- .../macos/Classes/FIAPRequestHandler.h | 2 +- .../macos/Classes/FIAPRequestHandler.m | 2 +- .../macos/Classes/FIAPaymentQueueHandler.h | 2 +- .../macos/Classes/FIAPaymentQueueHandler.m | 2 +- .../macos/Classes/FIATransactionCache.h | 2 +- .../macos/Classes/FIATransactionCache.m | 2 +- .../macos/Classes/InAppPurchasePlugin.h | 2 +- .../macos/Classes/InAppPurchasePlugin.m | 2 +- .../macos/in_app_purchase_storekit.podspec | 2 +- .../in_app_purchase_storekit/pubspec.yaml | 6 +- .../shared/Assets/.gitkeep | 0 .../test/fakes/fake_storekit_platform.dart | 26 +- .../sk_methodchannel_apis_test.dart | 2 +- packages/ios_platform_images/CHANGELOG.md | 4 + .../ios_platform_images/example/pubspec.yaml | 2 +- .../lib/ios_platform_images.dart | 6 +- packages/ios_platform_images/pubspec.yaml | 2 +- packages/local_auth/local_auth/CHANGELOG.md | 4 +- .../local_auth/example/lib/main.dart | 4 + .../local_auth/example/pubspec.yaml | 2 +- packages/local_auth/local_auth/pubspec.yaml | 2 +- .../local_auth_android/CHANGELOG.md | 4 + .../local_auth_android/android/build.gradle | 4 +- .../local_auth_android/example/lib/main.dart | 4 + .../local_auth_android/example/pubspec.yaml | 2 +- .../local_auth_android/pubspec.yaml | 2 +- .../local_auth/local_auth_ios/CHANGELOG.md | 4 + .../local_auth_ios/example/lib/main.dart | 4 + .../local_auth_ios/example/pubspec.yaml | 2 +- .../local_auth/local_auth_ios/pubspec.yaml | 2 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../local_auth_windows/CHANGELOG.md | 8 + .../local_auth_windows/example/lib/main.dart | 54 +- .../local_auth_windows/example/pubspec.yaml | 2 +- .../lib/local_auth_windows.dart | 66 +- .../lib/src/messages.g.dart | 81 ++ .../local_auth_windows/pigeons/copyright.txt | 3 + .../local_auth_windows/pigeons/messages.dart | 27 + .../local_auth_windows/pubspec.yaml | 5 +- .../test/local_auth_test.dart | 161 ++- .../local_auth_windows/windows/CMakeLists.txt | 4 +- .../local_auth_windows/windows/local_auth.h | 33 +- .../windows/local_auth_plugin.cpp | 111 +- .../local_auth_windows/windows/messages.g.cpp | 110 ++ .../local_auth_windows/windows/messages.g.h | 93 ++ .../windows/test/local_auth_plugin_test.cpp | 150 +-- .../local_auth_windows/windows/test/mocks.h | 19 - .../path_provider/path_provider/CHANGELOG.md | 6 + .../path_provider/path_provider/README.md | 2 +- .../integration_test/path_provider_test.dart | 15 +- .../path_provider/example/pubspec.yaml | 2 +- .../path_provider/lib/path_provider.dart | 96 +- .../path_provider/path_provider/pubspec.yaml | 13 +- .../path_provider_android/CHANGELOG.md | 4 + .../example/pubspec.yaml | 2 +- .../path_provider_android/pubspec.yaml | 2 +- .../.gitignore | 0 .../AUTHORS | 0 .../path_provider_foundation/CHANGELOG.md | 13 + .../LICENSE | 0 .../README.md | 4 +- .../darwin/Classes/PathProviderPlugin.swift | 67 + .../darwin/Classes/messages.g.swift | 60 + .../darwin}/RunnerTests/RunnerTests.swift | 65 +- .../darwin/path_provider_foundation.podspec | 25 + .../example/README.md | 0 .../integration_test/path_provider_test.dart | 0 .../example/ios/.gitignore | 2 + .../ios/Flutter/AppFrameworkInfo.plist | 6 +- .../example/ios/Flutter/Debug.xcconfig | 1 - .../example/ios/Flutter/Release.xcconfig | 1 - .../example/ios/Podfile | 6 +- .../ios/Runner.xcodeproj/project.pbxproj | 450 ++++--- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 7 +- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../example/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 6 + .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 22 +- .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 18 +- .../ios/Runner/Runner-Bridging-Header.h | 0 .../example/lib/main.dart | 10 + .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../example/macos/Podfile | 0 .../macos/Runner.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../example/macos}/RunnerTests/Info.plist | 0 .../example/pubspec.yaml | 6 +- .../example/test_driver/integration_test.dart | 0 .../ios/Classes/PathProviderPlugin.swift | 1 + .../ios/Classes/messages.g.swift | 1 + .../path_provider_foundation/ios/README.md | 4 + .../ios/path_provider_foundation.podspec | 1 + .../lib/messages.g.dart | 52 + .../lib/path_provider_foundation.dart} | 36 +- .../macos/Classes/PathProviderPlugin.swift | 1 + .../macos/Classes/messages.g.swift | 1 + .../path_provider_foundation/macos/README.md | 4 + .../macos/path_provider_foundation.podspec | 1 + .../pigeons/copyright.txt | 3 + .../pigeons/messages.dart | 17 +- .../pubspec.yaml | 20 +- .../test/messages_test.g.dart | 44 + .../test/path_provider_foundation_test.dart} | 118 +- .../path_provider_foundation_test.mocks.dart | 37 + .../path_provider_ios/CHANGELOG.md | 28 - .../integration_test/path_provider_test.dart | 69 - .../example/ios/Flutter/Debug.xcconfig | 2 - .../example/ios/Flutter/Release.xcconfig | 2 - .../example/ios/Runner/AppDelegate.h | 10 - .../example/ios/Runner/AppDelegate.m | 16 - .../example/ios/Runner/main.m | 13 - .../ios/RunnerTests/PathProviderTests.m | 18 - .../path_provider_ios/example/lib/main.dart | 133 -- .../path_provider_ios/example/pubspec.yaml | 30 - .../path_provider_ios/ios/Assets/.gitkeep | 0 .../ios/Classes/FLTPathProviderPlugin.m | 43 - .../ios/Classes/messages.g.h | 28 - .../ios/Classes/messages.g.m | 138 -- .../ios/path_provider_ios.podspec | 22 - .../path_provider_ios/lib/messages.g.dart | 124 -- .../path_provider_ios/pubspec.yaml | 34 - .../test/messages_test.g.dart | 88 -- .../test/path_provider_ios_test.dart | 115 -- .../path_provider_linux/CHANGELOG.md | 2 +- .../path_provider_linux/example/pubspec.yaml | 2 +- .../path_provider_linux/pubspec.yaml | 2 +- .../path_provider_macos/CHANGELOG.md | 91 -- .../path_provider_macos/README.md | 11 - .../lib/path_provider_macos.dart | 72 -- .../macos/Classes/PathProviderPlugin.swift | 47 - .../macos/path_provider_macos.podspec | 22 - .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../path_provider_windows/CHANGELOG.md | 4 + .../example/pubspec.yaml | 2 +- .../plugin_platform_interface/CHANGELOG.md | 4 + .../plugin_platform_interface/pubspec.yaml | 2 +- .../quick_actions/quick_actions/CHANGELOG.md | 4 + .../quick_actions/example/pubspec.yaml | 2 +- .../quick_actions/quick_actions/pubspec.yaml | 2 +- .../quick_actions_android/CHANGELOG.md | 4 + .../android/build.gradle | 2 +- .../example/android/app/build.gradle | 4 +- .../example/pubspec.yaml | 2 +- .../quick_actions_android/pubspec.yaml | 2 +- .../quick_actions_ios/CHANGELOG.md | 4 + .../quick_actions_ios/example/pubspec.yaml | 2 +- .../quick_actions_ios/pubspec.yaml | 2 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../shared_preferences/CHANGELOG.md | 10 + .../shared_preferences/example/lib/main.dart | 4 +- .../shared_preferences/example/pubspec.yaml | 2 +- .../shared_preferences/pubspec.yaml | 11 +- .../shared_preferences_android/CHANGELOG.md | 8 + .../android/build.gradle | 2 +- .../example/lib/main.dart | 4 +- .../example/pubspec.yaml | 2 +- .../shared_preferences_android/pubspec.yaml | 4 +- .../test/shared_preferences_android_test.dart | 15 +- .../shared_preferences_foundation}/AUTHORS | 0 .../CHANGELOG.md | 19 + .../shared_preferences_foundation}/LICENSE | 0 .../README.md | 4 +- .../Classes/SharedPreferencesPlugin.swift | 64 + .../darwin/Classes/messages.g.swift | 111 ++ .../darwin/Tests/RunnerTests.swift | 64 + .../shared_preferences_foundation.podspec} | 20 +- .../example/.gitignore | 4 +- .../example/README.md | 0 .../shared_preferences_test.dart | 2 +- .../example/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 6 +- .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + .../example/ios/Podfile | 5 +- .../ios/Runner.xcodeproj/project.pbxproj | 457 ++++--- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 9 +- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 17 + .../AppIcon.appiconset/Contents.json | 6 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 22 +- .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 18 +- .../ios/Runner/Runner-Bridging-Header.h} | 5 +- .../example/lib/main.dart | 4 +- .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../example/macos/Podfile | 0 .../macos/Runner.xcodeproj/project.pbxproj | 11 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../example/macos/RunnerTests/Info.plist | 0 .../example/pubspec.yaml | 8 +- .../example/test_driver/integration_test.dart | 0 .../ios/Classes/SharedPreferencesPlugin.swift | 1 + .../ios/Classes/messages.g.swift | 1 + .../ios/README.md | 4 + .../ios/shared_preferences_foundation.podspec | 1 + .../lib/messages.g.dart | 124 +- .../lib/shared_preferences_foundation.dart} | 9 +- .../Classes/SharedPreferencesPlugin.swift | 1 + .../macos/Classes/messages.g.swift | 1 + .../macos/README.md | 4 + .../shared_preferences_foundation.podspec | 1 + .../pigeons/copyright_header.txt | 0 .../pigeons/messages.dart | 9 +- .../pubspec.yaml | 18 +- .../shared_preferences_foundation_test.dart} | 36 +- .../test/test_api.g.dart} | 36 +- .../shared_preferences_ios/AUTHORS | 66 - .../shared_preferences_ios/CHANGELOG.md | 25 - .../shared_preferences_ios/LICENSE | 25 - .../shared_preferences_ios/example/.metadata | 10 - .../shared_preferences_ios/example/README.md | 9 - .../shared_preferences_test.dart | 106 -- .../example/ios/Runner/AppDelegate.h | 10 - .../example/ios/Runner/AppDelegate.m | 16 - .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 564 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 1283 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 1588 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 1025 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 1716 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 1920 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 1283 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 1895 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 2665 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 2665 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 3831 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 1888 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 3294 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 3612 -> 0 bytes .../example/ios/Runner/main.m | 13 - .../example/ios/RunnerTests/Info.plist | 22 - .../ios/RunnerTests/SharedPreferencesTests.m | 18 - .../example/lib/main.dart | 93 -- .../example/pubspec.yaml | 30 - .../example/test_driver/integration_test.dart | 7 - .../ios/Assets/.gitkeep | 0 .../ios/Classes/FLTSharedPreferencesPlugin.h | 8 - .../ios/Classes/FLTSharedPreferencesPlugin.m | 68 - .../ios/Classes/messages.g.h | 33 - .../ios/Classes/messages.g.m | 178 --- .../ios/shared_preferences_ios.podspec | 23 - .../shared_preferences_ios/pubspec.yaml | 27 - .../shared_preferences_linux/CHANGELOG.md | 8 + .../example/lib/main.dart | 4 +- .../example/pubspec.yaml | 2 +- .../shared_preferences_linux/pubspec.yaml | 4 +- .../shared_preferences_macos/AUTHORS | 66 - .../shared_preferences_macos/CHANGELOG.md | 81 -- .../shared_preferences_macos/LICENSE | 25 - .../shared_preferences_macos/README.md | 11 - .../example/README.md | 9 - .../example/macos/RunnerTests/Info.plist | 22 - .../macos/RunnerTests/RunnerTests.swift | 88 -- .../example/test_driver/integration_test.dart | 7 - .../lib/shared_preferences_macos.dart | 53 - .../Classes/SharedPreferencesPlugin.swift | 61 - .../test/shared_preferences_macos_test.dart | 117 -- .../CHANGELOG.md | 2 +- .../pubspec.yaml | 2 +- ...ethod_channel_shared_preferences_test.dart | 15 +- .../shared_preferences_web/CHANGELOG.md | 2 +- .../example/pubspec.yaml | 2 +- .../shared_preferences_web/pubspec.yaml | 2 +- .../shared_preferences_windows/CHANGELOG.md | 8 + .../example/lib/main.dart | 4 +- .../example/pubspec.yaml | 2 +- .../shared_preferences_windows/pubspec.yaml | 4 +- .../url_launcher/url_launcher/CHANGELOG.md | 8 + packages/url_launcher/url_launcher/README.md | 6 +- .../url_launcher/example/lib/basic.dart | 2 +- .../url_launcher/example/lib/encoding.dart | 6 + .../url_launcher/example/lib/files.dart | 4 +- .../url_launcher/example/lib/main.dart | 8 +- .../url_launcher/example/pubspec.yaml | 2 +- .../url_launcher/lib/src/legacy_api.dart | 1 - .../url_launcher/url_launcher/pubspec.yaml | 4 +- .../test/src/legacy_api_test.dart | 2 - .../url_launcher_android/CHANGELOG.md | 8 + .../example/lib/main.dart | 8 +- .../url_launcher_android/example/pubspec.yaml | 2 +- .../url_launcher_android/pubspec.yaml | 4 +- .../test/url_launcher_android_test.dart | 10 +- .../url_launcher_ios/CHANGELOG.md | 5 + .../url_launcher_ios/example/lib/main.dart | 10 +- .../url_launcher_ios/example/pubspec.yaml | 2 +- .../url_launcher_ios/pubspec.yaml | 4 +- .../url_launcher_linux/CHANGELOG.md | 5 + .../url_launcher_linux/example/lib/main.dart | 2 +- .../url_launcher_linux/example/pubspec.yaml | 2 +- .../url_launcher_linux/pubspec.yaml | 4 +- .../url_launcher_macos/CHANGELOG.md | 5 + .../url_launcher_macos/example/lib/main.dart | 2 +- .../url_launcher_macos/example/pubspec.yaml | 2 +- .../url_launcher_macos/pubspec.yaml | 4 +- .../CHANGELOG.md | 4 + .../lib/link.dart | 1 - .../pubspec.yaml | 2 +- .../url_launcher_web/CHANGELOG.md | 5 + .../url_launcher_web/example/pubspec.yaml | 2 +- .../url_launcher_web/lib/src/link.dart | 8 +- .../url_launcher_web/pubspec.yaml | 4 +- .../url_launcher_windows/CHANGELOG.md | 8 +- .../example/lib/main.dart | 2 +- .../url_launcher_windows/example/pubspec.yaml | 2 +- .../lib/src/messages.g.dart | 71 ++ .../lib/url_launcher_windows.dart | 34 +- .../pigeons/copyright.txt | 3 + .../pigeons/messages.dart | 18 + .../url_launcher_windows/pubspec.yaml | 5 +- .../test/url_launcher_windows_test.dart | 197 ++- .../windows/CMakeLists.txt | 2 + .../windows/messages.g.cpp | 113 ++ .../url_launcher_windows/windows/messages.g.h | 86 ++ .../windows/system_apis.cpp | 4 +- .../windows/system_apis.h | 4 +- .../test/url_launcher_windows_test.cpp | 82 +- .../windows/url_launcher_plugin.cpp | 53 +- .../windows/url_launcher_plugin.h | 19 +- .../windows/url_launcher_windows.cpp | 2 +- .../video_player/video_player/CHANGELOG.md | 12 + .../example/android/app/build.gradle | 2 +- .../video_player/example/pubspec.yaml | 2 +- .../video_player/lib/video_player.dart | 40 +- .../video_player/video_player/pubspec.yaml | 4 +- .../video_player_android/CHANGELOG.md | 4 + .../video_player_android/android/build.gradle | 2 +- .../example/android/app/build.gradle | 2 +- .../video_player_android/example/pubspec.yaml | 2 +- .../pigeons/messages.dart | 2 +- .../video_player_android/pubspec.yaml | 2 +- .../test/android_video_player_test.dart | 3 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../video_player_avfoundation/CHANGELOG.md | 4 + .../example/pubspec.yaml | 2 +- .../pigeons/messages.dart | 2 +- .../video_player_avfoundation/pubspec.yaml | 2 +- .../test/avfoundation_video_player_test.dart | 3 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../CHANGELOG.md | 4 + .../pubspec.yaml | 2 +- .../video_player_web/CHANGELOG.md | 4 + .../video_player_web/example/pubspec.yaml | 2 +- .../video_player_web/pubspec.yaml | 2 +- .../webview_flutter/CHANGELOG.md | 12 + .../webview_flutter/example/lib/main.dart | 72 +- .../lib/src/legacy/webview.dart | 1 + .../webview_flutter/lib/webview_flutter.dart | 1 + .../webview_flutter/pubspec.yaml | 2 +- .../test/webview_flutter_test.dart | 2 + .../webview_flutter_android/CHANGELOG.md | 30 + .../android/build.gradle | 2 +- .../FileChooserParamsFlutterApiImpl.java | 74 ++ .../GeneratedAndroidWebView.java | 675 +++++++--- .../webviewflutter/InstanceManager.java | 69 +- .../webviewflutter/JavaObjectHostApiImpl.java | 4 + .../WebChromeClientFlutterApiImpl.java | 27 + .../WebChromeClientHostApiImpl.java | 42 + .../webviewflutter/WebViewHostApiImpl.java | 4 +- .../webviewflutter/FileChooserParamsTest.java | 74 ++ .../webviewflutter/InstanceManagerTest.java | 49 + .../plugins/webviewflutter/WebViewTest.java | 33 + .../legacy/webview_flutter_test.dart | 29 +- .../webview_flutter_test.dart | 282 +++-- .../example/lib/main.dart | 72 +- .../example/pubspec.yaml | 1 + .../lib/src/android_navigation_delegate.dart | 318 ----- .../lib/src/android_proxy.dart | 5 + .../lib/src/android_webview.dart | 100 +- ...iew.pigeon.dart => android_webview.g.dart} | 1115 +++++++++-------- .../lib/src/android_webview_api_impls.dart | 71 +- .../lib/src/android_webview_controller.dart | 440 ++++++- .../lib/src/android_webview_platform.dart | 1 - .../src/legacy/webview_android_widget.dart | 22 +- .../lib/src/weak_reference_utils.dart | 2 +- .../lib/webview_flutter_android.dart | 1 - .../pigeons/android_webview.dart | 58 +- .../webview_flutter_android/pubspec.yaml | 4 +- .../android_navigation_delegate_test.dart | 17 +- .../test/android_webview_controller_test.dart | 126 +- ...android_webview_controller_test.mocks.dart | 129 +- .../test/android_webview_test.dart | 114 +- .../test/android_webview_test.mocks.dart | 29 +- .../test/legacy/surface_android_test.dart | 7 +- .../legacy/webview_android_widget_test.dart | 20 +- .../webview_android_widget_test.mocks.dart | 10 + ...igeon.dart => test_android_webview.g.dart} | 178 ++- .../CHANGELOG.md | 8 + .../lib/src/platform_navigation_delegate.dart | 7 + .../lib/src/platform_webview_controller.dart | 7 + .../src/platform_webview_cookie_manager.dart | 7 + .../lib/src/platform_webview_widget.dart | 7 + .../pubspec.yaml | 4 +- .../test/webview_platform_test.dart | 63 + .../webview_flutter_web/CHANGELOG.md | 8 + .../webview_flutter_web/README.md | 55 +- .../webview_flutter_web/example/pubspec.yaml | 1 + .../lib/src/web_webview_platform.dart | 4 +- .../webview_flutter_web/pubspec.yaml | 4 +- .../test/webview_flutter_web_test.dart | 17 + .../webview_flutter_wkwebview/CHANGELOG.md | 16 + .../webview_flutter_test.dart | 276 +++- .../example/lib/main.dart | 72 +- .../example/pubspec.yaml | 1 + .../{web_kit.pigeon.dart => web_kit.g.dart} | 0 .../src/foundation/foundation_api_impls.dart | 2 +- .../src/legacy/web_kit_webview_widget.dart | 6 +- .../lib/src/ui_kit/ui_kit_api_impls.dart | 2 +- .../lib/src/web_kit/web_kit_api_impls.dart | 4 +- .../lib/src/webkit_proxy.dart | 8 +- .../lib/src/webkit_webview_controller.dart | 40 +- .../pigeons/web_kit.dart | 4 +- .../webview_flutter_wkwebview/pubspec.yaml | 2 +- .../legacy/web_kit_webview_widget_test.dart | 58 +- ...eb_kit.pigeon.dart => test_web_kit.g.dart} | 2 +- .../test/src/foundation/foundation_test.dart | 4 +- .../src/foundation/foundation_test.mocks.dart | 5 +- .../test/src/ui_kit/ui_kit_test.dart | 2 +- .../test/src/ui_kit/ui_kit_test.mocks.dart | 5 +- .../test/src/web_kit/web_kit_test.dart | 12 +- .../test/src/web_kit/web_kit_test.mocks.dart | 5 +- .../test/webkit_webview_controller_test.dart | 23 +- .../test/webkit_webview_widget_test.dart | 13 +- ...app.yaml => exclude_all_packages_app.yaml} | 0 script/tool/CHANGELOG.md | 21 + .../lib/src/common/git_version_finder.dart | 3 +- .../tool/lib/src/common/package_command.dart | 52 +- .../lib/src/common/package_state_utils.dart | 3 + .../lib/src/common/pub_version_finder.dart | 12 +- .../src/create_all_packages_app_command.dart | 24 +- .../lib/src/dependabot_check_command.dart | 5 +- script/tool/lib/src/format_command.dart | 10 +- script/tool/lib/src/main.dart | 4 +- ...ommand.dart => podspec_check_command.dart} | 81 +- .../tool/lib/src/pubspec_check_command.dart | 91 +- script/tool/lib/src/readme_check_command.dart | 3 +- .../tool/lib/src/update_excerpts_command.dart | 18 +- script/tool/pubspec.yaml | 2 +- .../test/common/git_version_finder_test.dart | 8 +- .../test/common/package_command_test.dart | 96 +- .../common/package_looping_command_test.dart | 4 +- .../test/common/package_state_utils_test.dart | 2 + script/tool/test/format_command_test.dart | 17 +- .../tool/test/lint_podspecs_command_test.dart | 222 ---- .../tool/test/podspec_check_command_test.dart | 428 +++++++ .../tool/test/pubspec_check_command_test.dart | 173 ++- .../test/update_excerpts_command_test.dart | 25 +- script/tool/test/util.dart | 2 +- 872 files changed, 14298 insertions(+), 7768 deletions(-) create mode 100644 .ci/targets/ios_build_all_plugins.yaml rename .ci/targets/{mac_ios_platform_tests.yaml => ios_platform_tests.yaml} (100%) rename .ci/targets/{mac_build_all_plugins.yaml => macos_build_all_plugins.yaml} (77%) rename .ci/targets/{mac_lint_podspecs.yaml => macos_lint_podspecs.yaml} (59%) create mode 100644 .ci/targets/macos_platform_tests.yaml rename .ci/targets/{build_all_plugins.yaml => windows_build_all_plugins.yaml} (76%) delete mode 100644 packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java delete mode 100644 packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java create mode 100644 packages/camera/camera_android_camerax/lib/src/camera.dart rename packages/camera/camera_android_camerax/lib/src/{camerax_library.pigeon.dart => camerax_library.g.dart} (58%) create mode 100644 packages/camera/camera_android_camerax/lib/src/system_services.dart create mode 100644 packages/camera/camera_android_camerax/lib/src/use_case.dart create mode 100644 packages/camera/camera_android_camerax/test/camera_test.dart create mode 100644 packages/camera/camera_android_camerax/test/system_services_test.dart create mode 100644 packages/camera/camera_android_camerax/test/system_services_test.mocks.dart rename packages/camera/camera_android_camerax/test/{test_camerax_library.pigeon.dart => test_camerax_library.g.dart} (50%) rename packages/file_selector/file_selector_ios/test/{test_api.dart => test_api.g.dart} (100%) create mode 100644 packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart create mode 100644 packages/file_selector/file_selector_linux/linux/.gitignore create mode 100644 packages/file_selector/file_selector_macos/lib/src/messages.g.dart create mode 100644 packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift rename packages/{path_provider/path_provider_ios => file_selector/file_selector_macos}/pigeons/copyright.txt (100%) create mode 100644 packages/file_selector/file_selector_macos/pigeons/messages.dart create mode 100644 packages/file_selector/file_selector_macos/test/file_selector_macos_test.mocks.dart create mode 100644 packages/file_selector/file_selector_macos/test/messages_test.g.dart rename packages/file_selector/file_selector_windows/test/{test_api.dart => test_api.g.dart} (100%) rename packages/google_sign_in/google_sign_in_web/lib/src/{generated => js_interop}/gapi.dart (90%) rename packages/google_sign_in/google_sign_in_web/lib/src/{generated => js_interop}/gapiauth2.dart (97%) rename packages/image_picker/image_picker_ios/test/{test_api.dart => test_api.g.dart} (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAObjectTranslator.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAObjectTranslator.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPPaymentQueueDelegate.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPPaymentQueueDelegate.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPReceiptManager.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPReceiptManager.m (97%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPRequestHandler.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPRequestHandler.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPaymentQueueHandler.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPaymentQueueHandler.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIATransactionCache.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIATransactionCache.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/InAppPurchasePlugin.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/InAppPurchasePlugin.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/in_app_purchase_storekit.podspec (100%) delete mode 120000 packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep delete mode 120000 packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/shared/Assets/.gitkeep create mode 100644 packages/local_auth/local_auth_windows/lib/src/messages.g.dart create mode 100644 packages/local_auth/local_auth_windows/pigeons/copyright.txt create mode 100644 packages/local_auth/local_auth_windows/pigeons/messages.dart create mode 100644 packages/local_auth/local_auth_windows/windows/messages.g.cpp create mode 100644 packages/local_auth/local_auth_windows/windows/messages.g.h rename packages/path_provider/{path_provider_macos => path_provider_foundation}/.gitignore (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/AUTHORS (100%) create mode 100644 packages/path_provider/path_provider_foundation/CHANGELOG.md rename packages/path_provider/{path_provider_ios => path_provider_foundation}/LICENSE (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/README.md (78%) create mode 100644 packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift create mode 100644 packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift rename packages/path_provider/{path_provider_macos/example/macos => path_provider_foundation/darwin}/RunnerTests/RunnerTests.swift (60%) create mode 100644 packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/README.md (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/integration_test/path_provider_test.dart (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/.gitignore (95%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Flutter/AppFrameworkInfo.plist (86%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Flutter/Debug.xcconfig (58%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Flutter/Release.xcconfig (58%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Podfile (95%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner.xcodeproj/project.pbxproj (60%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename packages/path_provider/{path_provider_ios/example/ios/Runner.xcworkspace => path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace}/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (95%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/path_provider/{path_provider_macos/example/macos => path_provider_foundation/example/ios}/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/AppDelegate.swift (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (94%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (51%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/ios/Runner/Info.plist (78%) rename packages/{shared_preferences/shared_preferences_ios => path_provider/path_provider_foundation}/example/ios/Runner/Runner-Bridging-Header.h (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/lib/main.dart (88%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Flutter/Flutter-Debug.xcconfig (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Flutter/Flutter-Release.xcconfig (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Podfile (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner.xcodeproj/project.pbxproj (99%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/{shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace => path_provider/path_provider_foundation/example/macos/Runner.xcworkspace}/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/AppDelegate.swift (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Configs/AppInfo.xcconfig (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Configs/Release.xcconfig (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/DebugProfile.entitlements (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Info.plist (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/MainFlutterWindow.swift (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/macos/Runner/Release.entitlements (100%) rename packages/path_provider/{path_provider_ios/example/ios => path_provider_foundation/example/macos}/RunnerTests/Info.plist (100%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/example/pubspec.yaml (88%) rename packages/path_provider/{path_provider_ios => path_provider_foundation}/example/test_driver/integration_test.dart (100%) create mode 120000 packages/path_provider/path_provider_foundation/ios/Classes/PathProviderPlugin.swift create mode 120000 packages/path_provider/path_provider_foundation/ios/Classes/messages.g.swift create mode 100644 packages/path_provider/path_provider_foundation/ios/README.md create mode 120000 packages/path_provider/path_provider_foundation/ios/path_provider_foundation.podspec create mode 100644 packages/path_provider/path_provider_foundation/lib/messages.g.dart rename packages/path_provider/{path_provider_ios/lib/path_provider_ios.dart => path_provider_foundation/lib/path_provider_foundation.dart} (51%) create mode 120000 packages/path_provider/path_provider_foundation/macos/Classes/PathProviderPlugin.swift create mode 120000 packages/path_provider/path_provider_foundation/macos/Classes/messages.g.swift create mode 100644 packages/path_provider/path_provider_foundation/macos/README.md create mode 120000 packages/path_provider/path_provider_foundation/macos/path_provider_foundation.podspec create mode 100644 packages/path_provider/path_provider_foundation/pigeons/copyright.txt rename packages/path_provider/{path_provider_ios => path_provider_foundation}/pigeons/messages.dart (63%) rename packages/path_provider/{path_provider_macos => path_provider_foundation}/pubspec.yaml (52%) create mode 100644 packages/path_provider/path_provider_foundation/test/messages_test.g.dart rename packages/path_provider/{path_provider_macos/test/path_provider_macos_test.dart => path_provider_foundation/test/path_provider_foundation_test.dart} (50%) create mode 100644 packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.mocks.dart delete mode 100644 packages/path_provider/path_provider_ios/CHANGELOG.md delete mode 100644 packages/path_provider/path_provider_ios/example/integration_test/path_provider_test.dart delete mode 100644 packages/path_provider/path_provider_ios/example/ios/Flutter/Debug.xcconfig delete mode 100644 packages/path_provider/path_provider_ios/example/ios/Flutter/Release.xcconfig delete mode 100644 packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.h delete mode 100644 packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.m delete mode 100644 packages/path_provider/path_provider_ios/example/ios/Runner/main.m delete mode 100644 packages/path_provider/path_provider_ios/example/ios/RunnerTests/PathProviderTests.m delete mode 100644 packages/path_provider/path_provider_ios/example/lib/main.dart delete mode 100644 packages/path_provider/path_provider_ios/example/pubspec.yaml delete mode 100644 packages/path_provider/path_provider_ios/ios/Assets/.gitkeep delete mode 100644 packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m delete mode 100644 packages/path_provider/path_provider_ios/ios/Classes/messages.g.h delete mode 100644 packages/path_provider/path_provider_ios/ios/Classes/messages.g.m delete mode 100644 packages/path_provider/path_provider_ios/ios/path_provider_ios.podspec delete mode 100644 packages/path_provider/path_provider_ios/lib/messages.g.dart delete mode 100644 packages/path_provider/path_provider_ios/pubspec.yaml delete mode 100644 packages/path_provider/path_provider_ios/test/messages_test.g.dart delete mode 100644 packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart delete mode 100644 packages/path_provider/path_provider_macos/CHANGELOG.md delete mode 100644 packages/path_provider/path_provider_macos/README.md delete mode 100644 packages/path_provider/path_provider_macos/lib/path_provider_macos.dart delete mode 100644 packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift delete mode 100644 packages/path_provider/path_provider_macos/macos/path_provider_macos.podspec rename packages/{path_provider/path_provider_macos => shared_preferences/shared_preferences_foundation}/AUTHORS (100%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md rename packages/{path_provider/path_provider_macos => shared_preferences/shared_preferences_foundation}/LICENSE (100%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/README.md (77%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift create mode 100644 packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift create mode 100644 packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift rename packages/shared_preferences/{shared_preferences_macos/macos/shared_preferences_macos.podspec => shared_preferences_foundation/darwin/shared_preferences_foundation.podspec} (52%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/.gitignore (92%) rename packages/{path_provider/path_provider_macos => shared_preferences/shared_preferences_foundation}/example/README.md (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/integration_test/shared_preferences_test.dart (98%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/.gitignore rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Flutter/AppFrameworkInfo.plist (86%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Release.xcconfig rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Podfile (95%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner.xcodeproj/project.pbxproj (59%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename packages/shared_preferences/{shared_preferences_ios/example/ios/Runner.xcworkspace => shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace}/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (94%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/shared_preferences/{shared_preferences_macos/example/macos => shared_preferences_foundation/example/ios}/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/AppDelegate.swift rename packages/{path_provider/path_provider_ios => shared_preferences/shared_preferences_foundation}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (94%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (51%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/example/ios/Runner/Info.plist (78%) rename packages/{path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.h => shared_preferences/shared_preferences_foundation/example/ios/Runner/Runner-Bridging-Header.h} (63%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/lib/main.dart (95%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Flutter/Flutter-Debug.xcconfig (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Flutter/Flutter-Release.xcconfig (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Podfile (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner.xcodeproj/project.pbxproj (99%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (99%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) create mode 100644 packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/AppDelegate.swift (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Configs/AppInfo.xcconfig (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Configs/Release.xcconfig (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/DebugProfile.entitlements (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Info.plist (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/MainFlutterWindow.swift (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/macos/Runner/Release.entitlements (100%) rename packages/{path_provider/path_provider_macos => shared_preferences/shared_preferences_foundation}/example/macos/RunnerTests/Info.plist (100%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/example/pubspec.yaml (78%) rename packages/{path_provider/path_provider_macos => shared_preferences/shared_preferences_foundation}/example/test_driver/integration_test.dart (100%) create mode 120000 packages/shared_preferences/shared_preferences_foundation/ios/Classes/SharedPreferencesPlugin.swift create mode 120000 packages/shared_preferences/shared_preferences_foundation/ios/Classes/messages.g.swift create mode 100644 packages/shared_preferences/shared_preferences_foundation/ios/README.md create mode 120000 packages/shared_preferences/shared_preferences_foundation/ios/shared_preferences_foundation.podspec rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/lib/messages.g.dart (53%) rename packages/shared_preferences/{shared_preferences_ios/lib/shared_preferences_ios.dart => shared_preferences_foundation/lib/shared_preferences_foundation.dart} (85%) create mode 120000 packages/shared_preferences/shared_preferences_foundation/macos/Classes/SharedPreferencesPlugin.swift create mode 120000 packages/shared_preferences/shared_preferences_foundation/macos/Classes/messages.g.swift create mode 100644 packages/shared_preferences/shared_preferences_foundation/macos/README.md create mode 120000 packages/shared_preferences/shared_preferences_foundation/macos/shared_preferences_foundation.podspec rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/pigeons/copyright_header.txt (100%) rename packages/shared_preferences/{shared_preferences_ios => shared_preferences_foundation}/pigeons/messages.dart (63%) rename packages/shared_preferences/{shared_preferences_macos => shared_preferences_foundation}/pubspec.yaml (52%) rename packages/shared_preferences/{shared_preferences_ios/test/shared_preferences_ios_test.dart => shared_preferences_foundation/test/shared_preferences_foundation_test.dart} (65%) rename packages/shared_preferences/{shared_preferences_ios/test/messages.g.dart => shared_preferences_foundation/test/test_api.g.dart} (88%) delete mode 100644 packages/shared_preferences/shared_preferences_ios/AUTHORS delete mode 100644 packages/shared_preferences/shared_preferences_ios/CHANGELOG.md delete mode 100644 packages/shared_preferences/shared_preferences_ios/LICENSE delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/.metadata delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/README.md delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/integration_test/shared_preferences_test.dart delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.h delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.m delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/Runner/main.m delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/Info.plist delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/SharedPreferencesTests.m delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/lib/main.dart delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/pubspec.yaml delete mode 100644 packages/shared_preferences/shared_preferences_ios/example/test_driver/integration_test.dart delete mode 100644 packages/shared_preferences/shared_preferences_ios/ios/Assets/.gitkeep delete mode 100644 packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.h delete mode 100644 packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.m delete mode 100644 packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.h delete mode 100644 packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.m delete mode 100644 packages/shared_preferences/shared_preferences_ios/ios/shared_preferences_ios.podspec delete mode 100644 packages/shared_preferences/shared_preferences_ios/pubspec.yaml delete mode 100644 packages/shared_preferences/shared_preferences_macos/AUTHORS delete mode 100644 packages/shared_preferences/shared_preferences_macos/CHANGELOG.md delete mode 100644 packages/shared_preferences/shared_preferences_macos/LICENSE delete mode 100644 packages/shared_preferences/shared_preferences_macos/README.md delete mode 100644 packages/shared_preferences/shared_preferences_macos/example/README.md delete mode 100644 packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/Info.plist delete mode 100644 packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift delete mode 100644 packages/shared_preferences/shared_preferences_macos/example/test_driver/integration_test.dart delete mode 100644 packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart delete mode 100644 packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift delete mode 100644 packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart create mode 100644 packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart create mode 100644 packages/url_launcher/url_launcher_windows/pigeons/copyright.txt create mode 100644 packages/url_launcher/url_launcher_windows/pigeons/messages.dart create mode 100644 packages/url_launcher/url_launcher_windows/windows/messages.g.cpp create mode 100644 packages/url_launcher/url_launcher_windows/windows/messages.g.h rename packages/video_player/video_player_android/test/{test_api.dart => test_api.g.dart} (100%) rename packages/video_player/video_player_avfoundation/test/{test_api.dart => test_api.g.dart} (100%) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java delete mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart rename packages/webview_flutter/webview_flutter_android/lib/src/{android_webview.pigeon.dart => android_webview.g.dart} (68%) rename packages/webview_flutter/webview_flutter_android/test/{test_android_webview.pigeon.dart => test_android_webview.g.dart} (94%) create mode 100644 packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart rename packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/{web_kit.pigeon.dart => web_kit.g.dart} (100%) rename packages/webview_flutter/webview_flutter_wkwebview/test/src/common/{test_web_kit.pigeon.dart => test_web_kit.g.dart} (99%) rename script/configs/{exclude_all_plugins_app.yaml => exclude_all_packages_app.yaml} (100%) rename script/tool/lib/src/{lint_podspecs_command.dart => podspec_check_command.dart} (54%) delete mode 100644 script/tool/test/lint_podspecs_command_test.dart create mode 100644 script/tool/test/podspec_check_command_test.dart diff --git a/.ci.yaml b/.ci.yaml index c77aa42c2af1..c5b6adff9108 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -25,6 +25,17 @@ platform_properties: ] device_type: none os: Windows + mac_arm64: + properties: + dependencies: >- + [ + {"dependency": "xcode", "version": "14a5294e"}, + {"dependency": "gems", "version": "v3.3.14"} + ] + os: Mac-12 + device_type: none + cpu: arm64 + xcode: 14a5294e # xcode 14.0 beta 5 mac_x64: properties: dependencies: >- @@ -36,23 +47,24 @@ platform_properties: device_type: none cpu: x86 xcode: 14a5294e # xcode 14.0 beta 5 - + targets: ### iOS+macOS tasks *** # TODO(stuartmorgan): Move this to ARM once google_maps_flutter has ARM # support. `pod lint` makes a synthetic target that doesn't respect the # pod's arch exclusions, so fails to build. + # When moving it, rename the task and file to check_podspecs - name: Mac_x64 lint_podspecs recipe: plugins/plugins timeout: 30 properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_lint_podspecs.yaml + target_file: macos_lint_podspecs.yaml ### macOS desktop tasks ### - # macos-platform_tests builds all the plugins on M1, so this build is run + # macos-platform_tests builds all the plugins on ARM, so this build is run # on Intel to give us build coverage of both host types. - name: Mac_x64 build_all_plugins master recipe: plugins/plugins @@ -60,7 +72,7 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_build_all_plugins.yaml + target_file: macos_build_all_plugins.yaml channel: master - name: Mac_x64 build_all_plugins stable @@ -69,97 +81,267 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_build_all_plugins.yaml + target_file: macos_build_all_plugins.yaml channel: stable + - name: Mac_arm64 macos_platform_tests master + recipe: plugins/plugins + timeout: 60 + properties: + channel: master + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: macos_platform_tests.yaml + + - name: Mac_arm64 macos_platform_tests stable + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: macos_platform_tests.yaml + ### iOS tasks ### - # TODO(stuartmorgan): Swap this and ios-build_all_plugins once simulator - # tests are reliable on the ARM infrastructure. See discussion at - # https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 - - name: Mac_x64 ios_platform_tests_1_of_4 master + # TODO(stuartmorgan): Swap the architecture of this and ios_platform_tests_* + # once simulator tests are reliable on the ARM infrastructure. See discussion + # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 + - name: Mac_arm64 ios_build_all_plugins master recipe: plugins/plugins timeout: 30 properties: + channel: master add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_build_all_plugins.yaml + + - name: Mac_arm64 ios_build_all_plugins stable + recipe: plugins/plugins + timeout: 30 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_build_all_plugins.yaml + + - name: Mac_x64 ios_build_all_plugins master + bringup: true # New task, replaces ARM version + recipe: plugins/plugins + timeout: 30 + properties: + channel: master + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_build_all_plugins.yaml + + - name: Mac_x64 ios_build_all_plugins stable + bringup: true # New task, replaces ARM version + recipe: plugins/plugins + timeout: 30 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_build_all_plugins.yaml + + # TODO(stuartmorgan): Swap the architecture of this and ios_build_all_plugins + # once simulator tests are reliable on the ARM infrastructure. See discussion + # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 + - name: Mac_x64 ios_platform_tests_1_of_4 master + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 0 --shardCount 4" - name: Mac_x64 ios_platform_tests_2_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 1 --shardCount 4" - name: Mac_x64 ios_platform_tests_3_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 2 --shardCount 4" - name: Mac_x64 ios_platform_tests_4_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 4" + - name: Mac_arm64 ios_platform_tests_shard_1 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 0 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_2 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 1 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_3 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 2 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_4 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 3 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_5 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 4 --shardCount 5" + # Don't run full platform tests on both channels in pre-submit. - name: Mac_x64 ios_platform_tests_1_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 0 --shardCount 4" - name: Mac_x64 ios_platform_tests_2_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 1 --shardCount 4" - name: Mac_x64 ios_platform_tests_3_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 2 --shardCount 4" - name: Mac_x64 ios_platform_tests_4_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 4" + - name: Mac_arm64 ios_platform_tests_shard_1 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 0 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_2 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 1 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_3 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 2 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_4 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 3 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_5 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 4 --shardCount 5" + - name: Windows win32-platform_tests master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml @@ -172,7 +354,8 @@ targets: - name: Windows win32-platform_tests stable recipe: plugins/plugins - timeout: 30 + presubmit: false + timeout: 60 properties: add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml @@ -188,7 +371,7 @@ targets: timeout: 30 properties: add_recipes_cq: "true" - target_file: build_all_plugins.yaml + target_file: windows_build_all_plugins.yaml channel: master version_file: flutter_master.version dependencies: > @@ -201,7 +384,7 @@ targets: timeout: 30 properties: add_recipes_cq: "true" - target_file: build_all_plugins.yaml + target_file: windows_build_all_plugins.yaml channel: stable version_file: flutter_stable.version dependencies: > diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 48f779f4e341..339a8e70347e 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -dbc9306380d8a72273b478b8fcc934a6014f946d +c5e8757fcb795867f9f1ef42d7c2e98a74d6a231 diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index f3933394e20a..542569bcfd31 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -135454af32477f815a7525073027a3ff9eff1bfd +7048ed95a5ad3e43d697e0c397464193991fc230 diff --git a/.ci/scripts/build_all_plugins.sh b/.ci/scripts/build_all_plugins.sh index 89dab629fd52..c22b9832ff22 100644 --- a/.ci/scripts/build_all_plugins.sh +++ b/.ci/scripts/build_all_plugins.sh @@ -5,5 +5,6 @@ platform="$1" build_mode="$2" +shift 2 cd all_packages -flutter build "$platform" --"$build_mode" +flutter build "$platform" --"$build_mode" "$@" diff --git a/.ci/scripts/create_all_plugins_app.sh b/.ci/scripts/create_all_plugins_app.sh index 100e8aca804a..8399e5e38a35 100644 --- a/.ci/scripts/create_all_plugins_app.sh +++ b/.ci/scripts/create_all_plugins_app.sh @@ -4,4 +4,4 @@ # found in the LICENSE file. dart ./script/tool/bin/flutter_plugin_tools.dart create-all-packages-app \ - --output-dir=. --exclude script/configs/exclude_all_plugins_app.yaml + --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml diff --git a/.ci/targets/ios_build_all_plugins.yaml b/.ci/targets/ios_build_all_plugins.yaml new file mode 100644 index 000000000000..7b5b88d9c9ff --- /dev/null +++ b/.ci/targets/ios_build_all_plugins.yaml @@ -0,0 +1,11 @@ +tasks: + - name: prepare tool + script: .ci/scripts/prepare_tool.sh + - name: create all_plugins app + script: .ci/scripts/create_all_plugins_app.sh + - name: build all_plugins for iOS debug + script: .ci/scripts/build_all_plugins.sh + args: ["ios", "debug", "--no-codesign"] + - name: build all_plugins for iOS release + script: .ci/scripts/build_all_plugins.sh + args: ["ios", "release", "--no-codesign"] diff --git a/.ci/targets/mac_ios_platform_tests.yaml b/.ci/targets/ios_platform_tests.yaml similarity index 100% rename from .ci/targets/mac_ios_platform_tests.yaml rename to .ci/targets/ios_platform_tests.yaml diff --git a/.ci/targets/mac_build_all_plugins.yaml b/.ci/targets/macos_build_all_plugins.yaml similarity index 77% rename from .ci/targets/mac_build_all_plugins.yaml rename to .ci/targets/macos_build_all_plugins.yaml index 4dd324e8b3f0..e6eb8ac2c315 100644 --- a/.ci/targets/mac_build_all_plugins.yaml +++ b/.ci/targets/macos_build_all_plugins.yaml @@ -3,9 +3,9 @@ tasks: script: .ci/scripts/prepare_tool.sh - name: create all_plugins app script: .ci/scripts/create_all_plugins_app.sh - - name: build all_plugins debug + - name: build all_plugins for macOS debug script: .ci/scripts/build_all_plugins.sh args: ["macos", "debug"] - - name: build all_plugins release + - name: build all_plugins for macOS release script: .ci/scripts/build_all_plugins.sh args: ["macos", "release"] diff --git a/.ci/targets/mac_lint_podspecs.yaml b/.ci/targets/macos_lint_podspecs.yaml similarity index 59% rename from .ci/targets/mac_lint_podspecs.yaml rename to .ci/targets/macos_lint_podspecs.yaml index 02a904ee3d85..0b2217325635 100644 --- a/.ci/targets/mac_lint_podspecs.yaml +++ b/.ci/targets/macos_lint_podspecs.yaml @@ -1,6 +1,6 @@ tasks: - name: prepare tool script: .ci/scripts/prepare_tool.sh - - name: lint iOS and macOS podspecs + - name: validate iOS and macOS podspecs script: script/tool_runner.sh - args: ["podspecs"] + args: ["podspec-check"] diff --git a/.ci/targets/macos_platform_tests.yaml b/.ci/targets/macos_platform_tests.yaml new file mode 100644 index 000000000000..4b2ee4eac1fe --- /dev/null +++ b/.ci/targets/macos_platform_tests.yaml @@ -0,0 +1,19 @@ +tasks: + - name: prepare tool + script: .ci/scripts/prepare_tool.sh + - name: build examples + script: script/tool_runner.sh + args: ["build-examples", "--macos"] + - name: xcode analyze + script: script/tool_runner.sh + args: ["xcode-analyze", "--macos"] + - name: xcode analyze deprecation + # Ensure we don't accidentally introduce deprecated code. + script: script/tool_runner.sh + args: ["xcode-analyze", "--macos", "--macos-min-version=12.3"] + - name: native test + script: script/tool_runner.sh + args: ["native-test", "--macos"] + - name: drive examples + script: script/tool_runner.sh + args: ["drive-examples", "--macos", "--exclude=script/configs/exclude_integration_macos.yaml"] diff --git a/.ci/targets/build_all_plugins.yaml b/.ci/targets/windows_build_all_plugins.yaml similarity index 76% rename from .ci/targets/build_all_plugins.yaml rename to .ci/targets/windows_build_all_plugins.yaml index 0ffbdfcce376..53d6b99e2444 100644 --- a/.ci/targets/build_all_plugins.yaml +++ b/.ci/targets/windows_build_all_plugins.yaml @@ -3,9 +3,9 @@ tasks: script: .ci/scripts/prepare_tool.sh - name: create all_plugins app script: .ci/scripts/create_all_plugins_app.sh - - name: build all_plugins debug + - name: build all_plugins for Windows debug script: .ci/scripts/build_all_plugins.sh args: ["windows", "debug"] - - name: build all_plugins release + - name: build all_plugins for Windows release script: .ci/scripts/build_all_plugins.sh args: ["windows", "release"] diff --git a/.cirrus.yml b/.cirrus.yml index 5f3c5fe8b39a..8d659c3d72aa 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -22,21 +22,6 @@ tool_setup_template: &TOOL_SETUP_TEMPLATE tool_setup_script: - .ci/scripts/prepare_tool.sh -macos_template: &MACOS_TEMPLATE - # Only one macOS task can run in parallel without credits, so use them for - # PRs on macOS. - use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true' - -macos_intel_template: &MACOS_INTEL_TEMPLATE - << : *MACOS_TEMPLATE - osx_instance: - image: big-sur-xcode-13 - -macos_arm_template: &MACOS_ARM_TEMPLATE - << : *MACOS_TEMPLATE - macos_instance: - image: ghcr.io/cirruslabs/macos-ventura-xcode:14 - flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE upgrade_flutter_script: # Channels that are part of our normal test matrix use a pinned, @@ -62,17 +47,19 @@ flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE - flutter doctor -v << : *TOOL_SETUP_TEMPLATE -build_all_plugins_app_template: &BUILD_ALL_PLUGINS_APP_TEMPLATE - create_all_plugins_app_script: - - $PLUGIN_TOOL_COMMAND create-all-packages-app --output-dir=. --exclude script/configs/exclude_all_plugins_app.yaml - build_all_plugins_debug_script: +# Ensures that the latest versions of all of the 1P packages can be used +# together. See script/configs/exclude_all_packages_app.yaml for exceptions. +build_all_packages_app_template: &BUILD_ALL_PACKAGES_APP_TEMPLATE + create_all_packages_app_script: + - $PLUGIN_TOOL_COMMAND create-all-packages-app --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml + build_all_packages_debug_script: - cd all_packages - if [[ "$BUILD_ALL_ARGS" == "web" ]]; then - echo "Skipping; web does not support debug builds" - else - flutter build $BUILD_ALL_ARGS --debug - fi - build_all_plugins_release_script: + build_all_packages_release_script: - cd all_packages - flutter build $BUILD_ALL_ARGS --release @@ -101,7 +88,9 @@ task: always: format_script: ./script/tool_runner.sh format --fail-on-change license_script: $PLUGIN_TOOL_COMMAND license-check - pubspec_script: ./script/tool_runner.sh pubspec-check + # The major and minor versions here should match the lowest version + # analyzed in legacy_version_analyze. + pubspec_script: ./script/tool_runner.sh pubspec-check --min-min-flutter-version=3.0.0 --min-min-dart-version=2.17.0 readme_script: - ./script/tool_runner.sh readme-check # Re-run with --require-excerpts, skipping packages that still need @@ -130,13 +119,6 @@ task: - else - echo "Only run in presubmit" - fi - - name: dart_unit_tests - env: - matrix: - CHANNEL: "master" - CHANNEL: "stable" - unit_test_script: - - ./script/tool_runner.sh test - name: analyze env: matrix: @@ -171,12 +153,13 @@ task: - name: legacy_version_analyze depends_on: analyze matrix: + # Change the arguments to pubspec-check when changing these values. env: CHANNEL: "3.0.5" DART_VERSION: "2.17.6" env: - CHANNEL: "2.10.5" - DART_VERSION: "2.16.2" + CHANNEL: "3.3.10" + DART_VERSION: "2.18.6" package_prep_script: # Allow analyzing packages that use a dev dependency with a higher # minimum Flutter/Dart version than the package itself. @@ -198,21 +181,21 @@ task: CIRRUS_CLONE_SUBMODULES: true script: ./script/tool_runner.sh update-excerpts --fail-on-change ### Web tasks ### - - name: web-build_all_plugins + - name: web-build_all_packages env: BUILD_ALL_ARGS: "web" matrix: CHANNEL: "master" CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE + << : *BUILD_ALL_PACKAGES_APP_TEMPLATE ### Linux desktop tasks ### - - name: linux-build_all_plugins + - name: linux-build_all_packages env: BUILD_ALL_ARGS: "linux" matrix: CHANNEL: "master" CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE + << : *BUILD_ALL_PACKAGES_APP_TEMPLATE - name: linux-platform_tests # Don't run full platform tests on both channels in pre-submit. skip: $CIRRUS_PR != '' && $CHANNEL == 'stable' @@ -240,8 +223,16 @@ task: zone: us-central1-a namespace: default cpu: 4 - memory: 12G + memory: 16G matrix: + ### Platform-agnostic tasks ### + - name: dart_unit_tests + env: + matrix: + CHANNEL: "master" + CHANNEL: "stable" + unit_test_script: + - ./script/tool_runner.sh test ### Android tasks ### - name: android-platform_tests # Don't run full platform tests on both channels in pre-submit. @@ -263,7 +254,7 @@ task: lint_script: - ./script/tool_runner.sh lint-android # must come after build-examples native_unit_test_script: - # Native integration tests are handled by firebase-test-lab below, so + # Native integration tests are handled by Firebase Test Lab below, so # only run unit tests. # Must come after build-examples. - ./script/tool_runner.sh native-test --android --no-integration --exclude script/configs/exclude_native_unit_android.yaml @@ -280,13 +271,13 @@ task: path: "**/reports/lint-results-debug.xml" type: text/xml format: android-lint - - name: android-build_all_plugins + - name: android-build_all_packages env: BUILD_ALL_ARGS: "apk" matrix: CHANNEL: "master" CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE + << : *BUILD_ALL_PACKAGES_APP_TEMPLATE ### Web tasks ### - name: web-platform_tests env: @@ -303,37 +294,3 @@ task: - ./script/tool_runner.sh build-examples --web drive_script: - ./script/tool_runner.sh drive-examples --web --exclude=script/configs/exclude_integration_web.yaml - -# ARM macOS tasks. -task: - << : *MACOS_ARM_TEMPLATE - << : *FLUTTER_UPGRADE_TEMPLATE - matrix: - ### iOS tasks ### - - name: ios-build_all_plugins - env: - BUILD_ALL_ARGS: "ios --no-codesign" - matrix: - CHANNEL: "master" - CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE - ### macOS desktop tasks ### - - name: macos-platform_tests - # Don't run full platform tests on both channels in pre-submit. - skip: $CIRRUS_PR != '' && $CHANNEL == 'stable' - env: - matrix: - CHANNEL: "master" - CHANNEL: "stable" - PATH: $PATH:/usr/local/bin - build_script: - - ./script/tool_runner.sh build-examples --macos - xcode_analyze_script: - - ./script/tool_runner.sh xcode-analyze --macos - xcode_analyze_deprecation_script: - # Ensure we don't accidentally introduce deprecated code. - - ./script/tool_runner.sh xcode-analyze --macos --macos-min-version=12.3 - native_test_script: - - ./script/tool_runner.sh native-test --macos - drive_script: - - ./script/tool_runner.sh drive-examples --macos --exclude=script/configs/exclude_integration_macos.yaml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 292db55dede7..bbb153386ff2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: cd $GITHUB_WORKSPACE # Checks out a copy of the repo. - name: Check out code - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c with: fetch-depth: 0 # Fetch all history so the tool can get all the tags to determine version. - name: Set up tools diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 812888e2f1b7..f50f8d09dcd5 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -22,12 +22,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d + uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 with: results_file: results.sarif results_format: sarif diff --git a/CODEOWNERS b/CODEOWNERS index 04cc2b1c4468..f4d6ede3fc43 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,7 @@ packages/**/*_web/** @ditman # - Android packages/camera/camera_android/** @camsim99 +packages/camera/camera_android_camerax/** @camsim99 packages/espresso/** @GaryQian packages/flutter_plugin_android_lifecycle/** @GaryQian packages/google_maps_flutter/google_maps_flutter_android/** @GaryQian @@ -44,14 +45,14 @@ packages/video_player/video_player_android/** @camsim99 packages/camera/camera_avfoundation/** @hellohuanlin packages/file_selector/file_selector_ios/** @jmagman packages/google_maps_flutter/google_maps_flutter_ios/** @cyanglaz -packages/google_sign_in/google_sign_in_ios/** @jmagman -packages/image_picker/image_picker_ios/** @cyanglaz +packages/google_sign_in/google_sign_in_ios/** @vashworth +packages/image_picker/image_picker_ios/** @vashworth packages/in_app_purchase/in_app_purchase_storekit/** @cyanglaz packages/ios_platform_images/ios/** @jmagman -packages/local_auth/local_auth_ios/** @hellohuanlin -packages/path_provider/path_provider_ios/** @jmagman +packages/local_auth/local_auth_ios/** @louisehsu +packages/path_provider/path_provider_foundation/** @jmagman packages/quick_actions/quick_actions_ios/** @hellohuanlin -packages/shared_preferences/shared_preferences_ios/** @cyanglaz +packages/shared_preferences/shared_preferences_foundation/** @cyanglaz packages/url_launcher/url_launcher_ios/** @jmagman packages/video_player/video_player_avfoundation/** @hellohuanlin packages/webview_flutter/webview_flutter_wkwebview/** @cyanglaz @@ -64,8 +65,6 @@ packages/url_launcher/url_launcher_linux/** @cbracken # - macOS packages/file_selector/file_selector_macos/** @cbracken -packages/path_provider/path_provider_macos/** @cbracken -packages/shared_preferences/shared_preferences_macos/** @cbracken packages/url_launcher/url_launcher_macos/** @cbracken # - Windows diff --git a/README.md b/README.md index 92098af809e9..e7dcaf9be610 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://api.cirrus-ci.com/github/flutter/plugins.svg)](https://cirrus-ci.com/github/flutter/plugins/main) [![Release Status](https://github.com/flutter/plugins/actions/workflows/release.yml/badge.svg)](https://github.com/flutter/plugins/actions/workflows/release.yml) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/flutter/plugins/badge)](https://api.securityscorecards.dev/projects/github.com/flutter/plugins) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/flutter/plugins/badge)](https://deps.dev/project/github/flutter%2Fplugins) This repo is a companion repo to the main [flutter repo](https://github.com/flutter/flutter). It contains the source code for diff --git a/analysis_options.yaml b/analysis_options.yaml index 0e07b3324b81..498d19dfb4ae 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,55 +1,24 @@ -# This is a copy (as of March 2021) of flutter/flutter's analysis_options file, -# with minimal changes for this repository. The goal is to move toward using a -# shared set of analysis options as much as possible, and eventually a shared -# file. - # Specify analysis options. # -# For a list of lints, see: http://dart-lang.github.io/linter/lints/ -# See the configuration guide for more -# https://github.com/dart-lang/sdk/tree/main/pkg/analyzer#configuring-the-analyzer -# -# There are other similar analysis options files in the flutter repos, -# which should be kept in sync with this file: -# -# - analysis_options.yaml (this file) -# - packages/flutter/lib/analysis_options_user.yaml -# - https://github.com/flutter/flutter/blob/master/analysis_options.yaml -# - https://github.com/flutter/engine/blob/main/analysis_options.yaml -# - https://github.com/flutter/packages/blob/main/analysis_options.yaml -# -# This file contains the analysis options used for code in the flutter/plugins -# repository. +# This file is a copy of analysis_options.yaml from flutter repo +# as of 2022-07-27, but with some modifications marked with +# "DIFFERENT FROM FLUTTER/FLUTTER" below. The file is expected to +# be kept in sync with the master file from the flutter repo. analyzer: language: strict-casts: true strict-raw-types: true errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODO comments in the code - todo: ignore # allow self-reference to deprecated members (we do this because otherwise we have # to annotate every member in every test, assert, etc, when we deprecate something) deprecated_member_use_from_same_package: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore # Turned off until null-safe rollout is complete. unnecessary_null_comparison: ignore - ### Local flutter/plugins changes ### - # Allow null checks for as long as mixed mode is officially supported. - always_require_non_null_named_parameters: false # not needed with nnbd - exclude: + exclude: # DIFFERENT FROM FLUTTER/FLUTTER # Ignore generated files - '**/*.g.dart' - - 'lib/src/generated/*.dart' - '**/*.mocks.dart' # Mockito @GenerateMocks - - '**/*.pigeon.dart' # Pigeon generated file linter: rules: @@ -68,7 +37,7 @@ linter: # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/3023 - avoid_classes_with_only_static_members - avoid_double_and_int_checks - # - avoid_dynamic_calls # LOCAL CHANGE - Needs to be enabled and violations fixed. + - avoid_dynamic_calls - avoid_empty_else - avoid_equals_and_hash_code_on_mutable_classes - avoid_escaping_inner_quotes @@ -141,19 +110,19 @@ linter: # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/linter/issues/453 - missing_whitespace_between_adjacent_strings - no_adjacent_strings_in_list - # - no_default_cases # LOCAL CHANGE - Needs to be enabled and violations fixed. + - no_default_cases - no_duplicate_case_values - no_leading_underscores_for_library_prefixes - no_leading_underscores_for_local_identifiers - no_logic_in_create_state - # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - no_runtimeType_toString # DIFFERENT FROM FLUTTER/FLUTTER - non_constant_identifier_names - noop_primitive_operations - null_check_on_nullable_type_parameter - null_closures # - omit_local_variable_types # opposite of always_specify_types # - one_member_abstracts # too many false positives - # - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al # LOCAL CHANGE - Needs to be enabled and violations fixed. + - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al - overridden_fields - package_api_docs - package_names @@ -200,7 +169,7 @@ linter: - prefer_typing_uninitialized_variables - prefer_void_to_null - provide_deprecation_message - # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - public_member_api_docs # DIFFERENT FROM FLUTTER/FLUTTER - recursive_getters # - require_trailing_commas # blocked on https://github.com/dart-lang/sdk/issues/47441 - secure_pubspec_urls @@ -241,7 +210,7 @@ linter: - unnecessary_to_list_in_spreads - unrelated_type_equality_checks - unsafe_html - # - use_build_context_synchronously # LOCAL CHANGE - Needs to be enabled and violations fixed. + - use_build_context_synchronously # - use_colored_box # not yet tested # - use_decorated_box # not yet tested # - use_enums # not yet tested @@ -261,8 +230,3 @@ linter: # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - valid_regexps - void_checks - ### Local flutter/plugins additions ### - # These are from flutter/flutter/packages, so will need to be preserved - # separately when moving to a shared file. - - no_runtimeType_toString # use objectRuntimeType from package:foundation - - public_member_api_docs # see https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 84c7559d391b..13c00402449a 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.10.3 + +* Adds back use of Optional type. + +## 0.10.2+1 + +* Updates code for stricter lint checks. + +## 0.10.2 + +* Implements option to also stream when recording a video. + ## 0.10.1 * Remove usage of deprecated quiver Optional type. @@ -27,7 +39,7 @@ ## 0.10.0 * **Breaking Change** Bumps default camera_web package version, which updates permission exception code from `cameraPermission` to `CameraAccessDenied`. -* **Breaking Change** Bumps default camera_android package version, which updates permission exception code from `cameraPermission` to +* **Breaking Change** Bumps default camera_android package version, which updates permission exception code from `cameraPermission` to `CameraAccessDenied` and `AudioAccessDenied`. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 4911625ae08e..b343b6da9d89 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -31,9 +31,11 @@ IconData getCameraLensIcon(CameraLensDirection direction) { return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; - default: - throw ArgumentError('Unknown lens direction'); } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; } void _logError(String code, String? message) { diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index b05e61bef9f8..7a396c1589f9 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; @@ -160,10 +161,10 @@ class CameraValue { bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, - DeviceOrientation? lockedCaptureOrientation, - DeviceOrientation? recordingOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, bool? isPreviewPaused, - DeviceOrientation? previewPauseOrientation, + Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -180,12 +181,16 @@ class CameraValue { exposurePointSupported ?? this.exposurePointSupported, focusPointSupported: focusPointSupported ?? this.focusPointSupported, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation ?? this.lockedCaptureOrientation, - recordingOrientation: recordingOrientation ?? this.recordingOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - previewPauseOrientation: - previewPauseOrientation ?? this.previewPauseOrientation, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -353,8 +358,8 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, - previewPauseOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -367,7 +372,9 @@ class CameraController extends ValueNotifier { } try { await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith(isPreviewPaused: false); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -452,12 +459,6 @@ class CameraController extends ValueNotifier { assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); _throwIfNotInitialized('stopImageStream'); - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'stopImageStream was called while a video is being recorded.', - ); - } if (!value.isStreamingImages) { throw CameraException( 'No camera is streaming images', @@ -476,9 +477,13 @@ class CameraController extends ValueNotifier { /// Start a video recording. /// + /// You may optionally pass an [onAvailable] callback to also have the + /// video frames streamed to this callback. + /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. - Future startVideoRecording() async { + Future startVideoRecording( + {onLatestImageAvailable? onAvailable}) async { _throwIfNotInitialized('startVideoRecording'); if (value.isRecordingVideo) { throw CameraException( @@ -486,20 +491,23 @@ class CameraController extends ValueNotifier { 'startVideoRecording was called when a recording is already started.', ); } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ); + + Function(CameraImageData image)? streamCallback; + if (onAvailable != null) { + streamCallback = (CameraImageData imageData) { + onAvailable(CameraImage.fromPlatformInterface(imageData)); + }; } try { - await CameraPlatform.instance.startVideoRecording(_cameraId); + await CameraPlatform.instance.startVideoCapturing( + VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); value = value.copyWith( isRecordingVideo: true, isRecordingPaused: false, - recordingOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation), + isStreamingImages: onAvailable != null); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -516,10 +524,18 @@ class CameraController extends ValueNotifier { 'stopVideoRecording was called when no video is recording.', ); } + + if (value.isStreamingImages) { + stopImageStream(); + } + try { final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); + value = value.copyWith( + isRecordingVideo: false, + recordingOrientation: const Optional.absent(), + ); return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); @@ -737,7 +753,8 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.lockCaptureOrientation( _cameraId, orientation ?? value.deviceOrientation); value = value.copyWith( - lockedCaptureOrientation: orientation ?? value.deviceOrientation); + lockedCaptureOrientation: Optional.of( + orientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -757,7 +774,8 @@ class CameraController extends ValueNotifier { Future unlockCaptureOrientation() async { try { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); - value = value.copyWith(); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -828,3 +846,112 @@ class CameraController extends ValueNotifier { } } } + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 33d704ef651f..1b902ab61f0a 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.1 +version: 0.10.3 environment: sdk: ">=2.14.0 <3.0.0" @@ -21,10 +21,10 @@ flutter: default_package: camera_web dependencies: - camera_android: ^0.10.0 - camera_avfoundation: ^0.9.7+1 - camera_platform_interface: ^2.2.0 - camera_web: ^0.3.0 + camera_android: ^0.10.1 + camera_avfoundation: ^0.9.9 + camera_platform_interface: ^2.3.2 + camera_web: ^0.3.1 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 diff --git a/packages/camera/camera/test/camera_image_stream_test.dart b/packages/camera/camera/test/camera_image_stream_test.dart index a9320e46dfb5..29b5cceaa49a 100644 --- a/packages/camera/camera/test/camera_image_stream_test.dart +++ b/packages/camera/camera/test/camera_image_stream_test.dart @@ -130,7 +130,7 @@ void main() { ); }); - test('stopImageStream() throws $CameraException when recording videos', + test('stopImageStream() throws $CameraException when not streaming images', () async { final CameraController cameraController = CameraController( const CameraDescription( @@ -140,20 +140,16 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); - await cameraController.startImageStream((CameraImage image) => null); - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); expect( cameraController.stopImageStream, throwsA(isA().having( (CameraException error) => error.description, - 'A video recording is already started.', - 'stopImageStream was called while a video is being recorded.', + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', ))); }); - test('stopImageStream() throws $CameraException when not streaming images', - () async { + test('stopImageStream() intended behaviour', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', @@ -161,29 +157,44 @@ void main() { sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); + await cameraController.startImageStream((CameraImage image) => null); + await cameraController.stopImageStream(); + + expect(mockPlatform.streamCallLog, + ['onStreamedFrameAvailable', 'listen', 'cancel']); + }); + + test('startVideoRecording() can stream images', () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.startVideoRecording( + onAvailable: (CameraImage image) => null); expect( - cameraController.stopImageStream, - throwsA(isA().having( - (CameraException error) => error.description, - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ))); + mockPlatform.streamCallLog.contains('startVideoCapturing with stream'), + isTrue); }); - test('stopImageStream() intended behaviour', () async { + test('startVideoRecording() by default does not stream', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); + await cameraController.initialize(); - await cameraController.startImageStream((CameraImage image) => null); - await cameraController.stopImageStream(); - expect(mockPlatform.streamCallLog, - ['onStreamedFrameAvailable', 'listen', 'cancel']); + cameraController.startVideoRecording(); + + expect(mockPlatform.streamCallLog.contains('startVideoCapturing'), isTrue); }); } @@ -203,6 +214,24 @@ class MockStreamingCameraPlatform extends MockCameraPlatform { return _streamController!.stream; } + @override + Future startVideoRecording(int cameraId, + {Duration? maxVideoDuration}) { + streamCallLog.add('startVideoRecording'); + return super + .startVideoRecording(cameraId, maxVideoDuration: maxVideoDuration); + } + + @override + Future startVideoCapturing(VideoCaptureOptions options) { + if (options.streamCallback == null) { + streamCallLog.add('startVideoCapturing'); + } else { + streamCallLog.add('startVideoCapturing with stream'); + } + return super.startVideoCapturing(options); + } + void _onFrameStreamListen() { streamCallLog.add('listen'); } diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 546f4e925759..6677fcf90393 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -97,7 +97,8 @@ class FakeController extends ValueNotifier Future startImageStream(onLatestImageAvailable onAvailable) async {} @override - Future startVideoRecording() async {} + Future startVideoRecording( + {onLatestImageAvailable? onAvailable}) async {} @override Future stopImageStream() async {} @@ -132,8 +133,11 @@ void main() { isInitialized: true, isRecordingVideo: true, deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: DeviceOrientation.landscapeRight, - recordingOrientation: DeviceOrientation.landscapeLeft, + lockedCaptureOrientation: + const Optional.fromNullable( + DeviceOrientation.landscapeRight), + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); @@ -163,8 +167,11 @@ void main() { controller.value = controller.value.copyWith( isInitialized: true, deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: DeviceOrientation.landscapeRight, - recordingOrientation: DeviceOrientation.landscapeLeft, + lockedCaptureOrientation: + const Optional.fromNullable( + DeviceOrientation.landscapeRight), + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); @@ -194,7 +201,8 @@ void main() { controller.value = controller.value.copyWith( isInitialized: true, deviceOrientation: DeviceOrientation.portraitUp, - recordingOrientation: DeviceOrientation.landscapeLeft, + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 2138f2d055a5..ab8354f7ba05 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -335,30 +335,6 @@ void main() { ))); }); - test( - 'startVideoRecording() throws $CameraException when already streaming images', - () async { - final CameraController cameraController = CameraController( - const CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (CameraException error) => error.description, - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ))); - }); - test('getMaxZoomLevel() throws $CameraException when uninitialized', () async { final CameraController cameraController = CameraController( @@ -1190,7 +1166,8 @@ void main() { cameraController.value = cameraController.value.copyWith( isPreviewPaused: false, deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: DeviceOrientation.landscapeRight); + lockedCaptureOrientation: + Optional.of(DeviceOrientation.landscapeRight)); await cameraController.pausePreview(); @@ -1457,6 +1434,12 @@ class MockCameraPlatform extends Mock {Duration? maxVideoDuration}) => Future.value(mockVideoRecordingXFile); + @override + Future startVideoCapturing(VideoCaptureOptions options) { + return startVideoRecording(options.cameraId, + maxVideoDuration: options.maxDuration); + } + @override Future lockCaptureOrientation( int? cameraId, DeviceOrientation? orientation) async => diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 1b07c3005a4c..4609b402058a 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,25 @@ +## 0.10.4 + +* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case. + +## 0.10.3 + +* Adds back use of Optional type. +* Updates minimum Flutter version to 3.0. + +## 0.10.2+3 + +* Updates code for stricter lint checks. + +## 0.10.2+2 + +* Fixes zoom computation for virtual cameras hiding physical cameras in Android 11+. +* Removes the unused CameraZoom class from the codebase. + +## 0.10.2+1 + +* Updates code for stricter lint checks. + ## 0.10.2 * Remove usage of deprecated quiver Optional type. diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index 4fbb2270b556..7ed8ac77d371 100644 --- a/packages/camera/camera_android/android/build.gradle +++ b/packages/camera/camera_android/android/build.gradle @@ -63,7 +63,7 @@ android { dependencies { implementation 'androidx.annotation:annotation:1.5.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.robolectric:robolectric:4.5' } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index 7c592b9c7e99..b02d6864b5b7 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -258,8 +258,11 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { MediaRecorderBuilder mediaRecorderBuilder; - if (Build.VERSION.SDK_INT >= 31) { - mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath); + // TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null + // once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668 + EncoderProfiles recordingProfile = getRecordingProfile(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && recordingProfile != null) { + mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath); } else { mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath); } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java index 95efebbf6488..a69bae43ee17 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java @@ -150,10 +150,34 @@ public interface CameraProperties { * android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key. * * @return Float Maximum ratio between both active area width and crop region width, and active - * area height and crop region height + * area height and crop region height. */ Float getScalerAvailableMaxDigitalZoom(); + /** + * Returns the minimum ratio between the default camera zoom setting and all of the available + * zoom. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's lower value. + * + * @return Float Minimum ratio between the default zoom ratio and the minimum possible zoom. + */ + @RequiresApi(api = VERSION_CODES.R) + Float getScalerMinZoomRatio(); + + /** + * Returns the maximum ratio between the default camera zoom setting and all of the available + * zoom. + * + *

By default maps to the @see + * android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's upper value. + * + * @return Float Maximum ratio between the default zoom ratio and the maximum possible zoom. + */ + @RequiresApi(api = VERSION_CODES.R) + Float getScalerMaxZoomRatio(); + /** * Returns the area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied. @@ -315,6 +339,18 @@ public Float getScalerAvailableMaxDigitalZoom() { return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); } + @RequiresApi(api = VERSION_CODES.R) + @Override + public Float getScalerMaxZoomRatio() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getUpper(); + } + + @RequiresApi(api = VERSION_CODES.R) + @Override + public Float getScalerMinZoomRatio() { + return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getLower(); + } + @Override public Rect getSensorInfoActiveArraySize() { return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java deleted file mode 100644 index 42ad6d76dcfc..000000000000 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.camera; - -import android.graphics.Rect; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.math.MathUtils; - -public final class CameraZoom { - public static final float DEFAULT_ZOOM_FACTOR = 1.0f; - - @NonNull private final Rect cropRegion = new Rect(); - @Nullable private final Rect sensorSize; - - public final float maxZoom; - public final boolean hasSupport; - - public CameraZoom(@Nullable final Rect sensorArraySize, final Float maxZoom) { - this.sensorSize = sensorArraySize; - - if (this.sensorSize == null) { - this.maxZoom = DEFAULT_ZOOM_FACTOR; - this.hasSupport = false; - return; - } - - this.maxZoom = - ((maxZoom == null) || (maxZoom < DEFAULT_ZOOM_FACTOR)) ? DEFAULT_ZOOM_FACTOR : maxZoom; - - this.hasSupport = (Float.compare(this.maxZoom, DEFAULT_ZOOM_FACTOR) > 0); - } - - public Rect computeZoom(final float zoom) { - if (sensorSize == null || !this.hasSupport) { - return null; - } - - final float newZoom = MathUtils.clamp(zoom, DEFAULT_ZOOM_FACTOR, this.maxZoom); - - final int centerX = this.sensorSize.width() / 2; - final int centerY = this.sensorSize.height() / 2; - final int deltaX = (int) ((0.5f * this.sensorSize.width()) / newZoom); - final int deltaY = (int) ((0.5f * this.sensorSize.height()) / newZoom); - - this.cropRegion.set(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY); - - return cropRegion; - } -} diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java index afbd7c3758a6..0ec2fbef87de 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java @@ -114,19 +114,23 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) if (preset.ordinal() > ResolutionPreset.high.ordinal()) { preset = ResolutionPreset.high; } - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { EncoderProfiles profile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset); List videoProfiles = profile.getVideoProfiles(); EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0); - return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); - } else { - @SuppressWarnings("deprecation") - CamcorderProfile profile = - getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset); - return new Size(profile.videoFrameWidth, profile.videoFrameHeight); + if (defaultVideoProfile != null) { + return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); + } } + + @SuppressWarnings("deprecation") + // TODO(camsim99): Suppression is currently safe because legacy code is used as a fallback for SDK >= S. + // This should be removed when reverting that fallback behavior: https://github.com/flutter/flutter/issues/119668. + CamcorderProfile profile = + getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset); + return new Size(profile.videoFrameWidth, profile.videoFrameHeight); } /** @@ -234,15 +238,24 @@ private void configureResolution(ResolutionPreset resolutionPreset, int cameraId if (!checkIsSupported()) { return; } + boolean captureSizeCalculated = false; - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + recordingProfileLegacy = null; recordingProfile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset); List videoProfiles = recordingProfile.getVideoProfiles(); EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0); - captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); - } else { + + if (defaultVideoProfile != null) { + captureSizeCalculated = true; + captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); + } + } + + if (!captureSizeCalculated) { + recordingProfile = null; @SuppressWarnings("deprecation") CamcorderProfile camcorderProfile = getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java index 736fad4d92dc..2ac70822eb77 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java @@ -6,16 +6,18 @@ import android.graphics.Rect; import android.hardware.camera2.CaptureRequest; +import android.os.Build; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; /** Controls the zoom configuration on the {@link android.hardware.camera2} API. */ public class ZoomLevelFeature extends CameraFeature { - private static final float MINIMUM_ZOOM_LEVEL = 1.0f; + private static final Float DEFAULT_ZOOM_LEVEL = 1.0f; private final boolean hasSupport; private final Rect sensorArraySize; - private Float currentSetting = MINIMUM_ZOOM_LEVEL; - private Float maximumZoomLevel = MINIMUM_ZOOM_LEVEL; + private Float currentSetting = DEFAULT_ZOOM_LEVEL; + private Float minimumZoomLevel = currentSetting; + private Float maximumZoomLevel; /** * Creates a new instance of the {@link ZoomLevelFeature}. @@ -28,18 +30,24 @@ public ZoomLevelFeature(CameraProperties cameraProperties) { sensorArraySize = cameraProperties.getSensorInfoActiveArraySize(); if (sensorArraySize == null) { - maximumZoomLevel = MINIMUM_ZOOM_LEVEL; + maximumZoomLevel = minimumZoomLevel; hasSupport = false; return; } + // On Android 11+ CONTROL_ZOOM_RATIO_RANGE should be use to get the zoom ratio directly as minimum zoom does not have to be 1.0f. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + minimumZoomLevel = cameraProperties.getScalerMinZoomRatio(); + maximumZoomLevel = cameraProperties.getScalerMaxZoomRatio(); + } else { + minimumZoomLevel = DEFAULT_ZOOM_LEVEL; + Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom(); + maximumZoomLevel = + ((maxDigitalZoom == null) || (maxDigitalZoom < minimumZoomLevel)) + ? minimumZoomLevel + : maxDigitalZoom; + } - Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom(); - maximumZoomLevel = - ((maxDigitalZoom == null) || (maxDigitalZoom < MINIMUM_ZOOM_LEVEL)) - ? MINIMUM_ZOOM_LEVEL - : maxDigitalZoom; - - hasSupport = (Float.compare(maximumZoomLevel, MINIMUM_ZOOM_LEVEL) > 0); + hasSupport = (Float.compare(maximumZoomLevel, minimumZoomLevel) > 0); } @Override @@ -67,11 +75,19 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } - - final Rect computedZoom = - ZoomUtils.computeZoom( - currentSetting, sensorArraySize, MINIMUM_ZOOM_LEVEL, maximumZoomLevel); - requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom); + // On Android 11+ CONTROL_ZOOM_RATIO can be set to a zoom ratio and the camera feed will compute + // how to zoom on its own accounting for multiple logical cameras. + // Prior the image cropping window must be calculated and set manually. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + requestBuilder.set( + CaptureRequest.CONTROL_ZOOM_RATIO, + ZoomUtils.computeZoomRatio(currentSetting, minimumZoomLevel, maximumZoomLevel)); + } else { + final Rect computedZoom = + ZoomUtils.computeZoomRect( + currentSetting, sensorArraySize, minimumZoomLevel, maximumZoomLevel); + requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom); + } } /** @@ -80,7 +96,7 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) { * @return The minimum zoom level. */ public float getMinimumZoomLevel() { - return MINIMUM_ZOOM_LEVEL; + return minimumZoomLevel; } /** diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java index a4890b952cff..af9e48ff135a 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java @@ -18,7 +18,8 @@ final class ZoomUtils { * Computes an image sensor area based on the supplied zoom settings. * *

The returned image sensor area can be applied to the {@link android.hardware.camera2} API in - * order to control zoom levels. + * order to control zoom levels. This method of zoom should only be used for Android versions <= + * 11 as past that, the newer {@link #computeZoomRatio()} functional can be used. * * @param zoom The desired zoom level. * @param sensorArraySize The current area of the image sensor. @@ -26,9 +27,9 @@ final class ZoomUtils { * @param maximumZoomLevel The maximim supported zoom level. * @return An image sensor area based on the supplied zoom settings */ - static Rect computeZoom( + static Rect computeZoomRect( float zoom, @NonNull Rect sensorArraySize, float minimumZoomLevel, float maximumZoomLevel) { - final float newZoom = MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel); + final float newZoom = computeZoomRatio(zoom, minimumZoomLevel, maximumZoomLevel); final int centerX = sensorArraySize.width() / 2; final int centerY = sensorArraySize.height() / 2; @@ -37,4 +38,8 @@ static Rect computeZoom( return new Rect(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY); } + + static Float computeZoomRatio(float zoom, float minimumZoomLevel, float maximumZoomLevel) { + return MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel); + } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index 0aebfee39e0a..1f9f6200bb99 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -75,7 +75,7 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && encoderProfiles != null) { EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0); EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java index 40db12ee0fc3..c61be04465ab 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java @@ -201,6 +201,30 @@ public void getScalerAvailableMaxDigitalZoomTest() { assertEquals(actualDigitalZoom, expectedDigitalZoom); } + @Test + public void getScalerGetScalerMinZoomRatioTest() { + Range zoomRange = mock(Range.class); + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)) + .thenReturn(zoomRange); + + Float minZoom = cameraProperties.getScalerMinZoomRatio(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE); + assertEquals(zoomRange.getLower(), minZoom); + } + + @Test + public void getScalerGetScalerMaxZoomRatioTest() { + Range zoomRange = mock(Range.class); + when(mockCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)) + .thenReturn(zoomRange); + + Float maxZoom = cameraProperties.getScalerMaxZoomRatio(); + + verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE); + assertEquals(zoomRange.getUpper(), maxZoom); + } + @Test public void getSensorInfoActiveArraySizeTest() { Rect expectedArraySize = mock(Rect.class); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java deleted file mode 100644 index d3e495551608..000000000000 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.camera; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.graphics.Rect; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class CameraZoomTest { - - @Test - public void ctor_whenParametersAreValid() { - final Rect sensorSize = new Rect(0, 0, 0, 0); - final Float maxZoom = 4.0f; - final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); - - assertNotNull(cameraZoom); - assertTrue(cameraZoom.hasSupport); - assertEquals(4.0f, cameraZoom.maxZoom, 0); - assertEquals(1.0f, CameraZoom.DEFAULT_ZOOM_FACTOR, 0); - } - - @Test - public void ctor_whenSensorSizeIsNull() { - final Rect sensorSize = null; - final Float maxZoom = 4.0f; - final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); - - assertNotNull(cameraZoom); - assertFalse(cameraZoom.hasSupport); - assertEquals(cameraZoom.maxZoom, 1.0f, 0); - } - - @Test - public void ctor_whenMaxZoomIsNull() { - final Rect sensorSize = new Rect(0, 0, 0, 0); - final Float maxZoom = null; - final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); - - assertNotNull(cameraZoom); - assertFalse(cameraZoom.hasSupport); - assertEquals(cameraZoom.maxZoom, 1.0f, 0); - } - - @Test - public void ctor_whenMaxZoomIsSmallerThenDefaultZoomFactor() { - final Rect sensorSize = new Rect(0, 0, 0, 0); - final Float maxZoom = 0.5f; - final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); - - assertNotNull(cameraZoom); - assertFalse(cameraZoom.hasSupport); - assertEquals(cameraZoom.maxZoom, 1.0f, 0); - } - - @Test - public void setZoom_whenNoSupportShouldNotSetScalerCropRegion() { - final CameraZoom cameraZoom = new CameraZoom(null, null); - final Rect computedZoom = cameraZoom.computeZoom(2f); - - assertNull(computedZoom); - } - - @Test - public void setZoom_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() { - final Rect sensorSize = new Rect(0, 0, 0, 0); - final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f); - final Rect computedZoom = cameraZoom.computeZoom(18f); - - assertNotNull(computedZoom); - assertEquals(computedZoom.left, 0); - assertEquals(computedZoom.top, 0); - assertEquals(computedZoom.right, 0); - assertEquals(computedZoom.bottom, 0); - } - - @Test - public void setZoom_whenSensorSizeIsValidShouldReturnCropRegion() { - final Rect sensorSize = new Rect(0, 0, 100, 100); - final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f); - final Rect computedZoom = cameraZoom.computeZoom(18f); - - assertNotNull(computedZoom); - assertEquals(computedZoom.left, 48); - assertEquals(computedZoom.top, 48); - assertEquals(computedZoom.right, 52); - assertEquals(computedZoom.bottom, 52); - } - - @Test - public void setZoom_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() { - final Rect sensorSize = new Rect(0, 0, 100, 100); - final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f); - final Rect computedZoom = cameraZoom.computeZoom(25f); - - assertNotNull(computedZoom); - assertEquals(computedZoom.left, 45); - assertEquals(computedZoom.top, 45); - assertEquals(computedZoom.right, 55); - assertEquals(computedZoom.bottom, 55); - } - - @Test - public void setZoom_whenZoomIsSmallerThenMinZoomClampToMinZoom() { - final Rect sensorSize = new Rect(0, 0, 100, 100); - final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f); - final Rect computedZoom = cameraZoom.computeZoom(0.5f); - - assertNotNull(computedZoom); - assertEquals(computedZoom.left, 0); - assertEquals(computedZoom.top, 0); - assertEquals(computedZoom.right, 100); - assertEquals(computedZoom.bottom, 100); - } -} diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java index 957b57a66435..dbc352d697a4 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java @@ -5,20 +5,27 @@ package io.flutter.plugins.camera.features.resolution; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import android.media.CamcorderProfile; import android.media.EncoderProfiles; +import android.util.Size; import io.flutter.plugins.camera.CameraProperties; +import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockedStatic; +import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @@ -329,4 +336,95 @@ public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLow() { mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA)); } + + @Config(minSdk = 31) + @Test + public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNull() { + try (MockedStatic mockedResolutionFeature = + mockStatic(ResolutionFeature.class)) { + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class); + List videoProfiles = + new ArrayList() { + { + add(null); + } + }; + when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles); + return mockEncoderProfiles; + }); + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class); + mockCamcorderProfile.videoFrameWidth = 10; + mockCamcorderProfile.videoFrameHeight = 50; + return mockCamcorderProfile; + }); + mockedResolutionFeature + .when(() -> ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max)) + .thenCallRealMethod(); + + Size testPreviewSize = ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max); + assertEquals(testPreviewSize.getWidth(), 10); + assertEquals(testPreviewSize.getHeight(), 50); + } + } + + @Config(minSdk = 31) + @Test + public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() { + beforeLegacy(); + try (MockedStatic mockedResolutionFeature = + mockStatic(ResolutionFeature.class)) { + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class); + List videoProfiles = + new ArrayList() { + { + add(null); + } + }; + when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles); + return mockEncoderProfiles; + }); + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class); + return mockCamcorderProfile; + }); + + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ResolutionFeature resolutionFeature = + new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName); + + assertNotNull(resolutionFeature.getRecordingProfileLegacy()); + assertNull(resolutionFeature.getRecordingProfile()); + } + } } diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java index 9f05cc255a8b..4d5826967009 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java @@ -18,7 +18,10 @@ import android.graphics.Rect; import android.hardware.camera2.CaptureRequest; +import android.os.Build; import io.flutter.plugins.camera.CameraProperties; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -40,7 +43,7 @@ public void before() { mockSensorArray = mock(Rect.class); mockedStaticCameraZoom - .when(() -> ZoomUtils.computeZoom(anyFloat(), any(), anyFloat(), anyFloat())) + .when(() -> ZoomUtils.computeZoomRect(anyFloat(), any(), anyFloat(), anyFloat())) .thenReturn(mockZoomArea); } @@ -147,6 +150,22 @@ public void updateBuilder_shouldSetScalarCropRegionWhenCheckIsSupportIsTrue() { verify(mockBuilder, times(1)).set(CaptureRequest.SCALER_CROP_REGION, mockZoomArea); } + @Test + public void updateBuilder_shouldControlZoomRatioWhenCheckIsSupportIsTrue() throws Exception { + setSdkVersion(Build.VERSION_CODES.R); + when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); + when(mockCameraProperties.getScalerMaxZoomRatio()).thenReturn(42f); + when(mockCameraProperties.getScalerMinZoomRatio()).thenReturn(1.0f); + + ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); + + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + + zoomLevelFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_ZOOM_RATIO, 0.0f); + } + @Test public void getMinimumZoomLevel() { ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); @@ -163,4 +182,38 @@ public void getMaximumZoomLevel() { assertEquals(42f, zoomLevelFeature.getMaximumZoomLevel(), 0); } + + @Test + public void checkZoomLevelFeature_callsMaxDigitalZoomOnAndroidQ() throws Exception { + setSdkVersion(Build.VERSION_CODES.Q); + + when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); + + new ZoomLevelFeature(mockCameraProperties); + + verify(mockCameraProperties, times(0)).getScalerMaxZoomRatio(); + verify(mockCameraProperties, times(0)).getScalerMinZoomRatio(); + verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom(); + } + + @Test + public void checkZoomLevelFeature_callsScalarMaxZoomRatioOnAndroidR() throws Exception { + setSdkVersion(Build.VERSION_CODES.R); + when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); + + new ZoomLevelFeature(mockCameraProperties); + + verify(mockCameraProperties, times(1)).getScalerMaxZoomRatio(); + verify(mockCameraProperties, times(1)).getScalerMinZoomRatio(); + verify(mockCameraProperties, times(0)).getScalerAvailableMaxDigitalZoom(); + } + + static void setSdkVersion(int sdkVersion) throws Exception { + Field sdkInt = Build.VERSION.class.getField("SDK_INT"); + sdkInt.setAccessible(true); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(sdkInt, sdkInt.getModifiers() & ~Modifier.FINAL); + sdkInt.set(null, sdkVersion); + } } diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java index 28160ff30714..2f6160816d15 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java @@ -15,9 +15,9 @@ @RunWith(RobolectricTestRunner.class) public class ZoomUtilsTest { @Test - public void setZoom_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() { + public void setZoomRect_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() { final Rect sensorSize = new Rect(0, 0, 0, 0); - final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f); + final Rect computedZoom = ZoomUtils.computeZoomRect(18f, sensorSize, 1f, 20f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 0); @@ -27,9 +27,9 @@ public void setZoom_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() { } @Test - public void setZoom_whenSensorSizeIsValidShouldReturnCropRegion() { + public void setZoomRect_whenSensorSizeIsValidShouldReturnCropRegion() { final Rect sensorSize = new Rect(0, 0, 100, 100); - final Rect computedZoom = ZoomUtils.computeZoom(18f, sensorSize, 1f, 20f); + final Rect computedZoom = ZoomUtils.computeZoomRect(18f, sensorSize, 1f, 20f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 48); @@ -39,9 +39,9 @@ public void setZoom_whenSensorSizeIsValidShouldReturnCropRegion() { } @Test - public void setZoom_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() { + public void setZoomRect_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() { final Rect sensorSize = new Rect(0, 0, 100, 100); - final Rect computedZoom = ZoomUtils.computeZoom(25f, sensorSize, 1f, 10f); + final Rect computedZoom = ZoomUtils.computeZoomRect(25f, sensorSize, 1f, 10f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 45); @@ -51,9 +51,9 @@ public void setZoom_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() { } @Test - public void setZoom_whenZoomIsSmallerThenMinZoomClampToMinZoom() { + public void setZoomRect_whenZoomIsSmallerThenMinZoomClampToMinZoom() { final Rect sensorSize = new Rect(0, 0, 100, 100); - final Rect computedZoom = ZoomUtils.computeZoom(0.5f, sensorSize, 1f, 10f); + final Rect computedZoom = ZoomUtils.computeZoomRect(0.5f, sensorSize, 1f, 10f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 0); @@ -61,4 +61,25 @@ public void setZoom_whenZoomIsSmallerThenMinZoomClampToMinZoom() { assertEquals(computedZoom.right, 100); assertEquals(computedZoom.bottom, 100); } + + @Test + public void setZoomRatio_whenNewZoomGreaterThanMaxZoomClampToMaxZoom() { + final Float computedZoom = ZoomUtils.computeZoomRatio(21f, 1f, 20f); + assertNotNull(computedZoom); + assertEquals(computedZoom, 20f, 0.0f); + } + + @Test + public void setZoomRatio_whenNewZoomLesserThanMinZoomClampToMinZoom() { + final Float computedZoom = ZoomUtils.computeZoomRatio(0.7f, 1f, 20f); + assertNotNull(computedZoom); + assertEquals(computedZoom, 1f, 0.0f); + } + + @Test + public void setZoomRatio_whenNewZoomValidReturnNewZoom() { + final Float computedZoom = ZoomUtils.computeZoomRatio(2.0f, 1f, 20f); + assertNotNull(computedZoom); + assertEquals(computedZoom, 2.0f, 0.0f); + } } diff --git a/packages/camera/camera_android/example/lib/camera_controller.dart b/packages/camera/camera_android/example/lib/camera_controller.dart index 79bf4e8b01e1..8139dcdb0220 100644 --- a/packages/camera/camera_android/example/lib/camera_controller.dart +++ b/packages/camera/camera_android/example/lib/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; @@ -108,10 +109,10 @@ class CameraValue { bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, - DeviceOrientation? lockedCaptureOrientation, - DeviceOrientation? recordingOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, bool? isPreviewPaused, - DeviceOrientation? previewPauseOrientation, + Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -124,12 +125,16 @@ class CameraValue { exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation ?? this.lockedCaptureOrientation, - recordingOrientation: recordingOrientation ?? this.recordingOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - previewPauseOrientation: - previewPauseOrientation ?? this.previewPauseOrientation, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -257,14 +262,16 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, - previewPauseOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Resumes the current camera preview Future resumePreview() async { await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith(isPreviewPaused: false); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); } /// Captures an image and returns the file where it was saved. @@ -307,8 +314,8 @@ class CameraController extends ValueNotifier { isRecordingVideo: true, isRecordingPaused: false, isStreamingImages: streamCallback != null, - recordingOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Stops the video recording and returns the file where it was saved. @@ -324,6 +331,7 @@ class CameraController extends ValueNotifier { value = value.copyWith( isRecordingVideo: false, isRecordingPaused: false, + recordingOrientation: const Optional.absent(), ); return file; } @@ -392,12 +400,16 @@ class CameraController extends ValueNotifier { Future lockCaptureOrientation() async { await CameraPlatform.instance .lockCaptureOrientation(_cameraId, value.deviceOrientation); - value = value.copyWith(lockedCaptureOrientation: value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: + Optional.of(value.deviceOrientation)); } /// Unlocks the capture orientation. Future unlockCaptureOrientation() async { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); } /// Sets the focus mode for taking pictures. @@ -431,3 +443,112 @@ class CameraController extends ValueNotifier { } } } + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera_android/example/lib/main.dart b/packages/camera/camera_android/example/lib/main.dart index af9aab1a8a86..4d98aed9a4c2 100644 --- a/packages/camera/camera_android/example/lib/main.dart +++ b/packages/camera/camera_android/example/lib/main.dart @@ -35,9 +35,11 @@ IconData getCameraLensIcon(CameraLensDirection direction) { return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; - default: - throw ArgumentError('Unknown lens direction'); } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; } void _logError(String code, String? message) { @@ -1089,5 +1091,4 @@ Future main() async { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index 8c985d94fd5a..e23e31a886de 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: camera_android: diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 4b342eee08d5..9ab9b578616a 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -145,6 +145,7 @@ class AndroidCamera extends CameraPlatform { // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { + // ignore: only_throw_errors throw error; } completer.completeError( @@ -520,9 +521,14 @@ class AndroidCamera extends CameraPlatform { return 'always'; case FlashMode.torch: return 'torch'; - default: - throw ArgumentError('Unknown FlashMode value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'off'; } /// Returns the resolution preset as a String. @@ -540,18 +546,23 @@ class AndroidCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'max'; } /// Converts messages received from the native platform into device events. Future _handleDeviceMethodCall(MethodCall call) async { switch (call.method) { case 'orientation_changed': + final Map arguments = _getArgumentDictionary(call); _deviceEventStreamController.add(DeviceOrientationChangedEvent( - deserializeDeviceOrientation( - call.arguments['orientation']! as String))); + deserializeDeviceOrientation(arguments['orientation']! as String))); break; default: throw MissingPluginException(); @@ -566,21 +577,23 @@ class AndroidCamera extends CameraPlatform { Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraInitializedEvent( cameraId, - call.arguments['previewWidth']! as double, - call.arguments['previewHeight']! as double, - deserializeExposureMode(call.arguments['exposureMode']! as String), - call.arguments['exposurePointSupported']! as bool, - deserializeFocusMode(call.arguments['focusMode']! as String), - call.arguments['focusPointSupported']! as bool, + arguments['previewWidth']! as double, + arguments['previewHeight']! as double, + deserializeExposureMode(arguments['exposureMode']! as String), + arguments['exposurePointSupported']! as bool, + deserializeFocusMode(arguments['focusMode']! as String), + arguments['focusPointSupported']! as bool, )); break; case 'resolution_changed': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, - call.arguments['captureWidth']! as double, - call.arguments['captureHeight']! as double, + arguments['captureWidth']! as double, + arguments['captureHeight']! as double, )); break; case 'camera_closing': @@ -589,23 +602,32 @@ class AndroidCamera extends CameraPlatform { )); break; case 'video_recorded': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(VideoRecordedEvent( cameraId, - XFile(call.arguments['path']! as String), - call.arguments['maxVideoDuration'] != null - ? Duration( - milliseconds: call.arguments['maxVideoDuration']! as int) + XFile(arguments['path']! as String), + arguments['maxVideoDuration'] != null + ? Duration(milliseconds: arguments['maxVideoDuration']! as int) : null, )); break; case 'error': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraErrorEvent( cameraId, - call.arguments['description']! as String, + arguments['description']! as String, )); break; default: throw MissingPluginException(); } } + + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } } diff --git a/packages/camera/camera_android/lib/src/utils.dart b/packages/camera/camera_android/lib/src/utils.dart index 663ec6da7a97..8d58f7fe1297 100644 --- a/packages/camera/camera_android/lib/src/utils.dart +++ b/packages/camera/camera_android/lib/src/utils.dart @@ -29,9 +29,14 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; - default: - throw ArgumentError('Unknown DeviceOrientation value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'portraitUp'; } /// Returns the device orientation for a given String. diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 6f25af7d61fb..fb3371912911 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,11 +2,11 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.2 +version: 0.10.4 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index e35d0fd1beb4..d80bd9cac7a3 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -32,14 +32,15 @@ void main() { // registerWith is called very early in initialization the bindings won't // have been initialized. While registerWith could intialize them, that // could slow down startup, so instead the handler should be set up lazily. - final ByteData? response = await TestDefaultBinaryMessengerBinding - .instance!.defaultBinaryMessenger - .handlePlatformMessage( - AndroidCamera.deviceEventChannelName, - const StandardMethodCodec().encodeMethodCall(const MethodCall( - 'orientation_changed', - {'orientation': 'portraitDown'})), - (ByteData? data) {}); + final ByteData? response = + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .handlePlatformMessage( + AndroidCamera.deviceEventChannelName, + const StandardMethodCodec().encodeMethodCall(const MethodCall( + 'orientation_changed', + {'orientation': 'portraitDown'})), + (ByteData? data) {}); expect(response, null); }); @@ -421,7 +422,8 @@ void main() { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); for (int i = 0; i < 3; i++) { - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( AndroidCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall( @@ -504,11 +506,13 @@ void main() { ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { + final Map typedData = + (returnData[i] as Map).cast(); final CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name']! as String, + name: typedData['name']! as String, lensDirection: - parseCameraLensDirection(returnData[i]['lensFacing']! as String), - sensorOrientation: returnData[i]['sensorOrientation']! as int, + parseCameraLensDirection(typedData['lensFacing']! as String), + sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } @@ -1119,3 +1123,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ce2fb9046c69..d5355c60c751 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -4,3 +4,7 @@ * Adds CameraInfo class and removes unnecessary code from plugin. * Adds CameraSelector class. * Adds ProcessCameraProvider class. +* Bump CameraX version to 1.3.0-alpha02. +* Adds Camera and UseCase classes, along with methods for binding UseCases to a lifecycle with the ProcessCameraProvider. +* Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. +* Changes instance manager to allow the separate creation of identical objects. diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle index d830bf7cec7c..822c3f6e318e 100644 --- a/packages/camera/camera_android_camerax/android/build.gradle +++ b/packages/camera/camera_android_camerax/android/build.gradle @@ -56,13 +56,13 @@ android { dependencies { // CameraX core library using the camera2 implementation must use same version number. - def camerax_version = "1.3.0-alpha01" + def camerax_version = "1.3.0-alpha03" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" implementation 'com.google.guava:guava:31.1-android' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.robolectric:robolectric:4.8' } diff --git a/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml b/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml index ea4275c757cf..52012aaa6915 100644 --- a/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml +++ b/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml @@ -1,3 +1,8 @@ + + + + diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index b8fbaf539c32..c35394f01d82 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -6,16 +6,19 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.view.TextureRegistry; /** Platform implementation of the camera_plugin implemented with the CameraX library. */ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, ActivityAware { private InstanceManager instanceManager; private FlutterPluginBinding pluginBinding; private ProcessCameraProviderHostApiImpl processCameraProviderHostApi; + public SystemServicesHostApiImpl systemServicesHostApi; /** * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. @@ -24,7 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity */ public CameraAndroidCameraxPlugin() {} - void setUp(BinaryMessenger binaryMessenger, Context context) { + void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry textureRegistry) { // Set up instance manager. instanceManager = InstanceManager.open( @@ -36,23 +39,21 @@ void setUp(BinaryMessenger binaryMessenger, Context context) { // Set up Host APIs. GeneratedCameraXLibrary.CameraInfoHostApi.setup( binaryMessenger, new CameraInfoHostApiImpl(instanceManager)); - GeneratedCameraXLibrary.JavaObjectHostApi.setup( - binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); GeneratedCameraXLibrary.CameraSelectorHostApi.setup( binaryMessenger, new CameraSelectorHostApiImpl(binaryMessenger, instanceManager)); + GeneratedCameraXLibrary.JavaObjectHostApi.setup( + binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); processCameraProviderHostApi = new ProcessCameraProviderHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.ProcessCameraProviderHostApi.setup( binaryMessenger, processCameraProviderHostApi); + systemServicesHostApi = new SystemServicesHostApiImpl(binaryMessenger, instanceManager); + GeneratedCameraXLibrary.SystemServicesHostApi.setup(binaryMessenger, systemServicesHostApi); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { pluginBinding = flutterPluginBinding; - (new CameraAndroidCameraxPlugin()) - .setUp( - flutterPluginBinding.getBinaryMessenger(), - flutterPluginBinding.getApplicationContext()); } @Override @@ -66,7 +67,16 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { - updateContext(activityPluginBinding.getActivity()); + setUp( + pluginBinding.getBinaryMessenger(), + pluginBinding.getApplicationContext(), + pluginBinding.getTextureRegistry()); + updateContext(pluginBinding.getApplicationContext()); + processCameraProviderHostApi.setLifecycleOwner( + (LifecycleOwner) activityPluginBinding.getActivity()); + systemServicesHostApi.setActivity(activityPluginBinding.getActivity()); + systemServicesHostApi.setPermissionsRegistry( + activityPluginBinding::addRequestPermissionsResultListener); } @Override @@ -89,7 +99,7 @@ public void onDetachedFromActivity() { * Updates context that is used to fetch the corresponding instance of a {@code * ProcessCameraProvider}. */ - private void updateContext(Context context) { + public void updateContext(Context context) { if (processCameraProviderHostApi != null) { processCameraProviderHostApi.setContext(context); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java new file mode 100644 index 000000000000..a03548399485 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import androidx.camera.core.Camera; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraFlutterApi; + +public class CameraFlutterApiImpl extends CameraFlutterApi { + private final InstanceManager instanceManager; + + public CameraFlutterApiImpl(BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + void create(Camera camera, Reply reply) { + create(instanceManager.addHostCreatedInstance(camera), reply); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java index 7daba0d38d6a..d960b7fff70a 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.camera.core.CameraInfo; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoHostApi; +import java.util.Objects; public class CameraInfoHostApiImpl implements CameraInfoHostApi { private final InstanceManager instanceManager; @@ -17,7 +18,8 @@ public CameraInfoHostApiImpl(InstanceManager instanceManager) { @Override public Long getSensorRotationDegrees(@NonNull Long identifier) { - CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(identifier); + CameraInfo cameraInfo = + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier)); return Long.valueOf(cameraInfo.getSensorRotationDegrees()); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java new file mode 100644 index 000000000000..19b1ee569a9b --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java @@ -0,0 +1,120 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import android.Manifest; +import android.Manifest.permission; +import android.app.Activity; +import android.content.pm.PackageManager; +import androidx.annotation.VisibleForTesting; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +final class CameraPermissionsManager { + interface PermissionsRegistry { + @SuppressWarnings("deprecation") + void addListener( + io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener handler); + } + + interface ResultCallback { + void onResult(String errorCode, String errorDescription); + } + + /** + * Camera access permission errors handled when camera is created. See {@code MethodChannelCamera} + * in {@code camera/camera_platform_interface} for details. + */ + private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING = + "CameraPermissionsRequestOngoing"; + + private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE = + "Another request is ongoing and multiple requests cannot be handled at once."; + private static final String CAMERA_ACCESS_DENIED = "CameraAccessDenied"; + private static final String CAMERA_ACCESS_DENIED_MESSAGE = "Camera access permission was denied."; + private static final String AUDIO_ACCESS_DENIED = "AudioAccessDenied"; + private static final String AUDIO_ACCESS_DENIED_MESSAGE = "Audio access permission was denied."; + + private static final int CAMERA_REQUEST_ID = 9796; + @VisibleForTesting boolean ongoing = false; + + void requestPermissions( + Activity activity, + PermissionsRegistry permissionsRegistry, + boolean enableAudio, + ResultCallback callback) { + if (ongoing) { + callback.onResult( + CAMERA_PERMISSIONS_REQUEST_ONGOING, CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE); + return; + } + if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) { + permissionsRegistry.addListener( + new CameraRequestPermissionsListener( + (String errorCode, String errorDescription) -> { + ongoing = false; + callback.onResult(errorCode, errorDescription); + })); + ongoing = true; + ActivityCompat.requestPermissions( + activity, + enableAudio + ? new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO} + : new String[] {Manifest.permission.CAMERA}, + CAMERA_REQUEST_ID); + } else { + // Permissions already exist. Call the callback with success. + callback.onResult(null, null); + } + } + + private boolean hasCameraPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, permission.CAMERA) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean hasAudioPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED; + } + + @VisibleForTesting + @SuppressWarnings("deprecation") + static final class CameraRequestPermissionsListener + implements io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener { + + // There's no way to unregister permission listeners in the v1 embedding, so we'll be called + // duplicate times in cases where the user denies and then grants a permission. Keep track of if + // we've responded before and bail out of handling the callback manually if this is a repeat + // call. + boolean alreadyCalled = false; + + final ResultCallback callback; + + @VisibleForTesting + CameraRequestPermissionsListener(ResultCallback callback) { + this.callback = callback; + } + + @Override + public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) { + if (alreadyCalled || id != CAMERA_REQUEST_ID) { + return false; + } + + alreadyCalled = true; + // grantResults could be empty if the permissions request with the user is interrupted + // https://developer.android.com/reference/android/app/Activity#onRequestPermissionsResult(int,%20java.lang.String[],%20int[]) + if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { + callback.onResult(CAMERA_ACCESS_DENIED, CAMERA_ACCESS_DENIED_MESSAGE); + } else if (grantResults.length > 1 && grantResults[1] != PackageManager.PERMISSION_GRANTED) { + callback.onResult(AUDIO_ACCESS_DENIED, AUDIO_ACCESS_DENIED_MESSAGE); + } else { + callback.onResult(null, null); + } + return true; + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java index 9c559a72e63c..0528d584d26e 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java @@ -12,6 +12,7 @@ import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraSelectorHostApi; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class CameraSelectorHostApiImpl implements CameraSelectorHostApi { private final BinaryMessenger binaryMessenger; @@ -41,13 +42,15 @@ public void create(@NonNull Long identifier, Long lensFacing) { @Override public List filter(@NonNull Long identifier, @NonNull List cameraInfoIds) { - CameraSelector cameraSelector = (CameraSelector) instanceManager.getInstance(identifier); + CameraSelector cameraSelector = + (CameraSelector) Objects.requireNonNull(instanceManager.getInstance(identifier)); List cameraInfosForFilter = new ArrayList(); for (Number cameraInfoAsNumber : cameraInfoIds) { Long cameraInfoId = cameraInfoAsNumber.longValue(); - CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(cameraInfoId); + CameraInfo cameraInfo = + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId)); cameraInfosForFilter.add(cameraInfo); } @@ -57,7 +60,9 @@ public List filter(@NonNull Long identifier, @NonNull List cameraInf List filteredCameraInfosIds = new ArrayList(); for (CameraInfo cameraInfo : filteredCameraInfos) { - cameraInfoFlutterApiImpl.create(cameraInfo, result -> {}); + if (!instanceManager.containsInstance(cameraInfo)) { + cameraInfoFlutterApiImpl.create(cameraInfo, result -> {}); + } Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo); filteredCameraInfosIds.add(filteredCameraInfoId); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java index 8063866d2fc6..83c43a9d55d4 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java @@ -4,10 +4,23 @@ package io.flutter.plugins.camerax; +import android.app.Activity; import androidx.camera.core.CameraSelector; public class CameraXProxy { public CameraSelector.Builder createCameraSelectorBuilder() { return new CameraSelector.Builder(); } + + public CameraPermissionsManager createCameraPermissionsManager() { + return new CameraPermissionsManager(); + } + + public DeviceOrientationManager createDeviceOrientationManager( + Activity activity, + Boolean isFrontFacing, + int sensorOrientation, + DeviceOrientationManager.DeviceOrientationChangeCallback callback) { + return new DeviceOrientationManager(activity, isFrontFacing, sensorOrientation, callback); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java new file mode 100644 index 000000000000..ebcb86433f65 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java @@ -0,0 +1,329 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import io.flutter.embedding.engine.systemchannels.PlatformChannel; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; + +/** + * Support class to help to determine the media orientation based on the orientation of the device. + */ +public class DeviceOrientationManager { + + interface DeviceOrientationChangeCallback { + void onChange(DeviceOrientation newOrientation); + } + + private static final IntentFilter orientationIntentFilter = + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); + + private final Activity activity; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final DeviceOrientationChangeCallback deviceOrientationChangeCallback; + private PlatformChannel.DeviceOrientation lastOrientation; + private BroadcastReceiver broadcastReceiver; + + DeviceOrientationManager( + @NonNull Activity activity, + boolean isFrontFacing, + int sensorOrientation, + DeviceOrientationChangeCallback callback) { + this.activity = activity; + this.isFrontFacing = isFrontFacing; + this.sensorOrientation = sensorOrientation; + this.deviceOrientationChangeCallback = callback; + } + + /** + * Starts listening to the device's sensors or UI for orientation updates. + * + *

When orientation information is updated, the callback method of the {@link + * DeviceOrientationChangeCallback} is called with the new orientation. This latest value can also + * be retrieved through the {@link #getVideoOrientation()} accessor. + * + *

If the device's ACCELEROMETER_ROTATION setting is enabled the {@link + * DeviceOrientationManager} will report orientation updates based on the sensor information. If + * the ACCELEROMETER_ROTATION is disabled the {@link DeviceOrientationManager} will fallback to + * the deliver orientation updates based on the UI orientation. + */ + public void start() { + if (broadcastReceiver != null) { + return; + } + broadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleUIOrientationChange(); + } + }; + activity.registerReceiver(broadcastReceiver, orientationIntentFilter); + broadcastReceiver.onReceive(activity, null); + } + + /** Stops listening for orientation updates. */ + public void stop() { + if (broadcastReceiver == null) { + return; + } + activity.unregisterReceiver(broadcastReceiver); + broadcastReceiver = null; + } + + /** + * Returns the device's photo orientation in degrees based on the sensor orientation and the last + * known UI orientation. + * + *

Returns one of 0, 90, 180 or 270. + * + * @return The device's photo orientation in degrees. + */ + public int getPhotoOrientation() { + return this.getPhotoOrientation(this.lastOrientation); + } + + /** + * Returns the device's photo orientation in degrees based on the sensor orientation and the + * supplied {@link PlatformChannel.DeviceOrientation} value. + * + *

Returns one of 0, 90, 180 or 270. + * + * @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted + * into degrees. + * @return The device's photo orientation in degrees. + */ + public int getPhotoOrientation(PlatformChannel.DeviceOrientation orientation) { + int angle = 0; + // Fallback to device orientation when the orientation value is null. + if (orientation == null) { + orientation = getUIOrientation(); + } + + switch (orientation) { + case PORTRAIT_UP: + angle = 90; + break; + case PORTRAIT_DOWN: + angle = 270; + break; + case LANDSCAPE_LEFT: + angle = isFrontFacing ? 180 : 0; + break; + case LANDSCAPE_RIGHT: + angle = isFrontFacing ? 0 : 180; + break; + } + + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X). + // This has to be taken into account so the JPEG is rotated properly. + // For devices with orientation of 90, this simply returns the mapping from ORIENTATIONS. + // For devices with orientation of 270, the JPEG is rotated 180 degrees instead. + return (angle + sensorOrientation + 270) % 360; + } + + /** + * Returns the device's video orientation in clockwise degrees based on the sensor orientation and + * the last known UI orientation. + * + *

Returns one of 0, 90, 180 or 270. + * + * @return The device's video orientation in clockwise degrees. + */ + public int getVideoOrientation() { + return this.getVideoOrientation(this.lastOrientation); + } + + /** + * Returns the device's video orientation in clockwise degrees based on the sensor orientation and + * the supplied {@link PlatformChannel.DeviceOrientation} value. + * + *

Returns one of 0, 90, 180 or 270. + * + *

More details can be found in the official Android documentation: + * https://developer.android.com/reference/android/media/MediaRecorder#setOrientationHint(int) + * + *

See also: + * https://developer.android.com/training/camera2/camera-preview-large-screens#orientation_calculation + * + * @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted + * into degrees. + * @return The device's video orientation in clockwise degrees. + */ + public int getVideoOrientation(PlatformChannel.DeviceOrientation orientation) { + int angle = 0; + + // Fallback to device orientation when the orientation value is null. + if (orientation == null) { + orientation = getUIOrientation(); + } + + switch (orientation) { + case PORTRAIT_UP: + angle = 0; + break; + case PORTRAIT_DOWN: + angle = 180; + break; + case LANDSCAPE_LEFT: + angle = 270; + break; + case LANDSCAPE_RIGHT: + angle = 90; + break; + } + + if (isFrontFacing) { + angle *= -1; + } + + return (angle + sensorOrientation + 360) % 360; + } + + /** @return the last received UI orientation. */ + public PlatformChannel.DeviceOrientation getLastUIOrientation() { + return this.lastOrientation; + } + + /** + * Handles orientation changes based on change events triggered by the OrientationIntentFilter. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + */ + @VisibleForTesting + void handleUIOrientationChange() { + PlatformChannel.DeviceOrientation orientation = getUIOrientation(); + handleOrientationChange(orientation, lastOrientation, deviceOrientationChangeCallback); + lastOrientation = orientation; + } + + /** + * Handles orientation changes coming from either the device's sensors or the + * OrientationIntentFilter. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + */ + @VisibleForTesting + static void handleOrientationChange( + DeviceOrientation newOrientation, + DeviceOrientation previousOrientation, + DeviceOrientationChangeCallback callback) { + if (!newOrientation.equals(previousOrientation)) { + callback.onChange(newOrientation); + } + } + + /** + * Gets the current user interface orientation. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @return The current user interface orientation. + */ + @VisibleForTesting + PlatformChannel.DeviceOrientation getUIOrientation() { + final int rotation = getDisplay().getRotation(); + final int orientation = activity.getResources().getConfiguration().orientation; + + switch (orientation) { + case Configuration.ORIENTATION_PORTRAIT: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { + return PlatformChannel.DeviceOrientation.PORTRAIT_UP; + } else { + return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN; + } + case Configuration.ORIENTATION_LANDSCAPE: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { + return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT; + } else { + return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT; + } + default: + return PlatformChannel.DeviceOrientation.PORTRAIT_UP; + } + } + + /** + * Calculates the sensor orientation based on the supplied angle. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @param angle Orientation angle. + * @return The sensor orientation based on the supplied angle. + */ + @VisibleForTesting + PlatformChannel.DeviceOrientation calculateSensorOrientation(int angle) { + final int tolerance = 45; + angle += tolerance; + + // Orientation is 0 in the default orientation mode. This is portrait-mode for phones + // and landscape for tablets. We have to compensate for this by calculating the default + // orientation, and apply an offset accordingly. + int defaultDeviceOrientation = getDeviceDefaultOrientation(); + if (defaultDeviceOrientation == Configuration.ORIENTATION_LANDSCAPE) { + angle += 90; + } + // Determine the orientation + angle = angle % 360; + return new PlatformChannel.DeviceOrientation[] { + PlatformChannel.DeviceOrientation.PORTRAIT_UP, + PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT, + PlatformChannel.DeviceOrientation.PORTRAIT_DOWN, + PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT, + } + [angle / 90]; + } + + /** + * Gets the default orientation of the device. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @return The default orientation of the device. + */ + @VisibleForTesting + int getDeviceDefaultOrientation() { + Configuration config = activity.getResources().getConfiguration(); + int rotation = getDisplay().getRotation(); + if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) + && config.orientation == Configuration.ORIENTATION_LANDSCAPE) + || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) + && config.orientation == Configuration.ORIENTATION_PORTRAIT)) { + return Configuration.ORIENTATION_LANDSCAPE; + } else { + return Configuration.ORIENTATION_PORTRAIT; + } + } + + /** + * Gets an instance of the Android {@link android.view.Display}. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @return An instance of the Android {@link android.view.Display}. + */ + @SuppressWarnings("deprecation") + @VisibleForTesting + Display getDisplay() { + return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + } +} 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 041564c3bfcb..528870cc749c 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 @@ -13,6 +13,8 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -23,6 +25,78 @@ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class GeneratedCameraXLibrary { + /** Generated class from Pigeon that represents data sent in messages. */ + public static class CameraPermissionsErrorData { + private @NonNull String errorCode; + + public @NonNull String getErrorCode() { + return errorCode; + } + + public void setErrorCode(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"errorCode\" is null."); + } + this.errorCode = setterArg; + } + + private @NonNull String description; + + public @NonNull String getDescription() { + return description; + } + + public void setDescription(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"description\" is null."); + } + this.description = setterArg; + } + + /** Constructor is private to enforce null safety; use Builder. */ + private CameraPermissionsErrorData() {} + + public static final class Builder { + private @Nullable String errorCode; + + public @NonNull Builder setErrorCode(@NonNull String setterArg) { + this.errorCode = setterArg; + return this; + } + + private @Nullable String description; + + public @NonNull Builder setDescription(@NonNull String setterArg) { + this.description = setterArg; + return this; + } + + public @NonNull CameraPermissionsErrorData build() { + CameraPermissionsErrorData pigeonReturn = new CameraPermissionsErrorData(); + pigeonReturn.setErrorCode(errorCode); + pigeonReturn.setDescription(description); + return pigeonReturn; + } + } + + @NonNull + Map toMap() { + Map toMapResult = new HashMap<>(); + toMapResult.put("errorCode", errorCode); + toMapResult.put("description", description); + return toMapResult; + } + + static @NonNull CameraPermissionsErrorData fromMap(@NonNull Map map) { + CameraPermissionsErrorData pigeonResult = new CameraPermissionsErrorData(); + Object errorCode = map.get("errorCode"); + pigeonResult.setErrorCode((String) errorCode); + Object description = map.get("description"); + pigeonResult.setDescription((String) description); + return pigeonResult; + } + } + public interface Result { void success(T result); @@ -332,6 +406,16 @@ public interface ProcessCameraProviderHostApi { @NonNull List getAvailableCameraInfos(@NonNull Long identifier); + @NonNull + Long bindToLifecycle( + @NonNull Long identifier, + @NonNull Long cameraSelectorIdentifier, + @NonNull List useCaseIds); + + void unbind(@NonNull Long identifier, @NonNull List useCaseIds); + + void unbindAll(@NonNull Long identifier); + /** The codec used by ProcessCameraProviderHostApi. */ static MessageCodec getCodec() { return ProcessCameraProviderHostApiCodec.INSTANCE; @@ -405,6 +489,107 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + Number cameraSelectorIdentifierArg = (Number) args.get(1); + if (cameraSelectorIdentifierArg == null) { + throw new NullPointerException( + "cameraSelectorIdentifierArg unexpectedly null."); + } + List useCaseIdsArg = (List) args.get(2); + if (useCaseIdsArg == null) { + throw new NullPointerException("useCaseIdsArg unexpectedly null."); + } + Long output = + api.bindToLifecycle( + (identifierArg == null) ? null : identifierArg.longValue(), + (cameraSelectorIdentifierArg == null) + ? null + : cameraSelectorIdentifierArg.longValue(), + useCaseIdsArg); + wrapped.put("result", output); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + List useCaseIdsArg = (List) args.get(1); + if (useCaseIdsArg == null) { + throw new NullPointerException("useCaseIdsArg unexpectedly null."); + } + api.unbind( + (identifierArg == null) ? null : identifierArg.longValue(), useCaseIdsArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + api.unbindAll((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } @@ -445,6 +630,221 @@ public void create(@NonNull Long identifierArg, Reply callback) { } } + private static class CameraFlutterApiCodec extends StandardMessageCodec { + public static final CameraFlutterApiCodec INSTANCE = new CameraFlutterApiCodec(); + + private CameraFlutterApiCodec() {} + } + + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class CameraFlutterApi { + private final BinaryMessenger binaryMessenger; + + public CameraFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + + static MessageCodec getCodec() { + return CameraFlutterApiCodec.INSTANCE; + } + + public void create(@NonNull Long identifierArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.CameraFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Arrays.asList(identifierArg)), + channelReply -> { + callback.reply(null); + }); + } + } + + private static class SystemServicesHostApiCodec extends StandardMessageCodec { + public static final SystemServicesHostApiCodec INSTANCE = new SystemServicesHostApiCodec(); + + private SystemServicesHostApiCodec() {} + + @Override + protected Object readValueOfType(byte type, ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return CameraPermissionsErrorData.fromMap((Map) readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + if (value instanceof CameraPermissionsErrorData) { + stream.write(128); + writeValue(stream, ((CameraPermissionsErrorData) value).toMap()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface SystemServicesHostApi { + void requestCameraPermissions( + @NonNull Boolean enableAudio, Result result); + + void startListeningForDeviceOrientationChange( + @NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation); + + void stopListeningForDeviceOrientationChange(); + + /** The codec used by SystemServicesHostApi. */ + static MessageCodec getCodec() { + return SystemServicesHostApiCodec.INSTANCE; + } + + /** + * Sets up an instance of `SystemServicesHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, SystemServicesHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Boolean enableAudioArg = (Boolean) args.get(0); + if (enableAudioArg == null) { + throw new NullPointerException("enableAudioArg unexpectedly null."); + } + Result resultCallback = + new Result() { + public void success(CameraPermissionsErrorData result) { + wrapped.put("result", result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + wrapped.put("error", wrapError(error)); + reply.reply(wrapped); + } + }; + + api.requestCameraPermissions(enableAudioArg, resultCallback); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + reply.reply(wrapped); + } + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Boolean isFrontFacingArg = (Boolean) args.get(0); + if (isFrontFacingArg == null) { + throw new NullPointerException("isFrontFacingArg unexpectedly null."); + } + Number sensorOrientationArg = (Number) args.get(1); + if (sensorOrientationArg == null) { + throw new NullPointerException("sensorOrientationArg unexpectedly null."); + } + api.startListeningForDeviceOrientationChange( + isFrontFacingArg, + (sensorOrientationArg == null) ? null : sensorOrientationArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + api.stopListeningForDeviceOrientationChange(); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + + private static class SystemServicesFlutterApiCodec extends StandardMessageCodec { + public static final SystemServicesFlutterApiCodec INSTANCE = + new SystemServicesFlutterApiCodec(); + + private SystemServicesFlutterApiCodec() {} + } + + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class SystemServicesFlutterApi { + private final BinaryMessenger binaryMessenger; + + public SystemServicesFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + + static MessageCodec getCodec() { + return SystemServicesFlutterApiCodec.INSTANCE; + } + + public void onDeviceOrientationChanged(@NonNull String orientationArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(orientationArg)), + channelReply -> { + callback.reply(null); + }); + } + } + private static Map wrapError(Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java index 9b549d7bd1ea..8212d1267a19 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java @@ -122,16 +122,15 @@ public void addDartCreatedInstance(Object instance, long identifier) { /** * Adds a new instance that was instantiated from the host platform. * - *

If an instance has already been added, the identifier of the instance will be returned. + *

If an instance has already been added, this will replace it. {@code #containsInstance} can + * be used to check if the object has already been added to avoid this. * * @param instance the instance to be stored. * @return the unique identifier stored with instance. */ public long addHostCreatedInstance(Object instance) { assertManagerIsNotClosed(); - if (containsInstance(instance)) { - return getIdentifierForStrongReference(instance); - } + final long identifier = nextIdentifier++; addInstance(instance, identifier); return identifier; diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java index 19c5eb5b3f70..e7036e7090c1 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java @@ -6,20 +6,26 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.camera.core.Camera; import androidx.camera.core.CameraInfo; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.UseCase; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; import com.google.common.util.concurrent.ListenableFuture; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ProcessCameraProviderHostApi; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class ProcessCameraProviderHostApiImpl implements ProcessCameraProviderHostApi { private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; private Context context; + private LifecycleOwner lifecycleOwner; public ProcessCameraProviderHostApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager, Context context) { @@ -28,6 +34,10 @@ public ProcessCameraProviderHostApiImpl( this.context = context; } + public void setLifecycleOwner(LifecycleOwner lifecycleOwner) { + this.lifecycleOwner = lifecycleOwner; + } + /** * Sets the context that the {@code ProcessCameraProvider} will use to attach the lifecycle of the * camera to. @@ -40,8 +50,8 @@ public void setContext(Context context) { } /** - * Returns the instance of the ProcessCameraProvider to manage the lifecycle of the camera for the - * current {@code Context}. + * Returns the instance of the {@code ProcessCameraProvider} to manage the lifecycle of the camera + * for the current {@code Context}. */ @Override public void getInstance(GeneratedCameraXLibrary.Result result) { @@ -54,9 +64,9 @@ public void getInstance(GeneratedCameraXLibrary.Result result) { // Camera provider is now guaranteed to be available. ProcessCameraProvider processCameraProvider = processCameraProviderFuture.get(); + final ProcessCameraProviderFlutterApiImpl flutterApi = + new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager); if (!instanceManager.containsInstance(processCameraProvider)) { - final ProcessCameraProviderFlutterApiImpl flutterApi = - new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager); flutterApi.create(processCameraProvider, reply -> {}); } result.success(instanceManager.getIdentifierForStrongReference(processCameraProvider)); @@ -67,11 +77,11 @@ public void getInstance(GeneratedCameraXLibrary.Result result) { ContextCompat.getMainExecutor(context)); } - /** Returns cameras available to the ProcessCameraProvider. */ + /** Returns cameras available to the {@code ProcessCameraProvider}. */ @Override public List getAvailableCameraInfos(@NonNull Long identifier) { ProcessCameraProvider processCameraProvider = - (ProcessCameraProvider) instanceManager.getInstance(identifier); + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); List availableCameras = processCameraProvider.getAvailableCameraInfos(); List availableCamerasIds = new ArrayList(); @@ -79,9 +89,68 @@ public List getAvailableCameraInfos(@NonNull Long identifier) { new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); for (CameraInfo cameraInfo : availableCameras) { - cameraInfoFlutterApi.create(cameraInfo, result -> {}); + if (!instanceManager.containsInstance(cameraInfo)) { + cameraInfoFlutterApi.create(cameraInfo, result -> {}); + } availableCamerasIds.add(instanceManager.getIdentifierForStrongReference(cameraInfo)); } return availableCamerasIds; } + + /** + * Binds specified {@code UseCase}s to the lifecycle of the {@code LifecycleOwner} that + * corresponds to this instance and returns the instance of the {@code Camera} whose lifecycle + * that {@code LifecycleOwner} reflects. + */ + @Override + public Long bindToLifecycle( + @NonNull Long identifier, + @NonNull Long cameraSelectorIdentifier, + @NonNull List useCaseIds) { + ProcessCameraProvider processCameraProvider = + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); + CameraSelector cameraSelector = + (CameraSelector) + Objects.requireNonNull(instanceManager.getInstance(cameraSelectorIdentifier)); + UseCase[] useCases = new UseCase[useCaseIds.size()]; + for (int i = 0; i < useCaseIds.size(); i++) { + useCases[i] = + (UseCase) + Objects.requireNonNull( + instanceManager.getInstance(((Number) useCaseIds.get(i)).longValue())); + } + + Camera camera = + processCameraProvider.bindToLifecycle( + (LifecycleOwner) lifecycleOwner, cameraSelector, useCases); + + final CameraFlutterApiImpl cameraFlutterApi = + new CameraFlutterApiImpl(binaryMessenger, instanceManager); + if (!instanceManager.containsInstance(camera)) { + cameraFlutterApi.create(camera, result -> {}); + } + + return instanceManager.getIdentifierForStrongReference(camera); + } + + @Override + public void unbind(@NonNull Long identifier, @NonNull List useCaseIds) { + ProcessCameraProvider processCameraProvider = + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); + UseCase[] useCases = new UseCase[useCaseIds.size()]; + for (int i = 0; i < useCaseIds.size(); i++) { + useCases[i] = + (UseCase) + Objects.requireNonNull( + instanceManager.getInstance(((Number) useCaseIds.get(i)).longValue())); + } + processCameraProvider.unbind(useCases); + } + + @Override + public void unbindAll(@NonNull Long identifier) { + ProcessCameraProvider processCameraProvider = + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); + processCameraProvider.unbindAll(); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java new file mode 100644 index 000000000000..1e9f33b092bb --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi; + +public class SystemServicesFlutterApiImpl extends SystemServicesFlutterApi { + public SystemServicesFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + private final InstanceManager instanceManager; + + public void onDeviceOrientationChanged(String orientation, Reply reply) { + super.onDeviceOrientationChanged(orientation, reply); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java new file mode 100644 index 000000000000..e8eb715a7b3a --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java @@ -0,0 +1,112 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import android.app.Activity; +import androidx.annotation.VisibleForTesting; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraPermissionsErrorData; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesHostApi; + +public class SystemServicesHostApiImpl implements SystemServicesHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + + @VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy(); + @VisibleForTesting public DeviceOrientationManager deviceOrientationManager; + @VisibleForTesting public SystemServicesFlutterApiImpl systemServicesFlutterApi; + + private Activity activity; + private PermissionsRegistry permissionsRegistry; + + public SystemServicesHostApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + this.systemServicesFlutterApi = + new SystemServicesFlutterApiImpl(binaryMessenger, instanceManager); + } + + public void setActivity(Activity activity) { + this.activity = activity; + } + + public void setPermissionsRegistry(PermissionsRegistry permissionsRegistry) { + this.permissionsRegistry = permissionsRegistry; + } + + /** + * Requests camera permissions using an instance of a {@link CameraPermissionsManager}. + * + *

Will result with {@code null} if permissions were approved or there were no errors; + * otherwise, it will result with the error data explaining what went wrong. + */ + @Override + public void requestCameraPermissions( + Boolean enableAudio, Result result) { + CameraPermissionsManager cameraPermissionsManager = + cameraXProxy.createCameraPermissionsManager(); + cameraPermissionsManager.requestPermissions( + activity, + permissionsRegistry, + enableAudio, + (String errorCode, String description) -> { + if (errorCode == null) { + result.success(null); + } else { + // If permissions are ongoing or denied, error data will be sent to be handled. + CameraPermissionsErrorData errorData = + new CameraPermissionsErrorData.Builder() + .setErrorCode(errorCode) + .setDescription(description) + .build(); + result.success(errorData); + } + }); + } + + /** + * Starts listening for device orientation changes using an instace of a {@link + * DeviceOrientationManager}. + * + *

Whenever a change in device orientation is detected by the {@code DeviceOrientationManager}, + * the {@link SystemServicesFlutterApi} will be used to notify the Dart side. + */ + @Override + public void startListeningForDeviceOrientationChange( + Boolean isFrontFacing, Long sensorOrientation) { + deviceOrientationManager = + cameraXProxy.createDeviceOrientationManager( + activity, + isFrontFacing, + sensorOrientation.intValue(), + (DeviceOrientation newOrientation) -> { + systemServicesFlutterApi.onDeviceOrientationChanged( + serializeDeviceOrientation(newOrientation), reply -> {}); + }); + deviceOrientationManager.start(); + } + + /** Serializes {@code DeviceOrientation} into a String that the Dart side is able to recognize. */ + String serializeDeviceOrientation(DeviceOrientation orientation) { + return orientation.toString(); + } + + /** + * Tells the {@code deviceOrientationManager} to stop listening for orientation updates. + * + *

Has no effect if the {@code deviceOrientationManager} was never created to listen for device + * orientation updates. + */ + @Override + public void stopListeningForDeviceOrientationChange() { + if (deviceOrientationManager != null) { + deviceOrientationManager.stop(); + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java new file mode 100644 index 000000000000..d90bde953306 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java @@ -0,0 +1,89 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.pm.PackageManager; +import io.flutter.plugins.camerax.CameraPermissionsManager.CameraRequestPermissionsListener; +import io.flutter.plugins.camerax.CameraPermissionsManager.ResultCallback; +import org.junit.Test; + +public class CameraPermissionsManagerTest { + @Test + public void listener_respondsOnce() { + final int[] calledCounter = {0}; + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener((String code, String desc) -> calledCounter[0]++); + + permissionsListener.onRequestPermissionsResult( + 9796, null, new int[] {PackageManager.PERMISSION_DENIED}); + permissionsListener.onRequestPermissionsResult( + 9796, null, new int[] {PackageManager.PERMISSION_GRANTED}); + + assertEquals(1, calledCounter[0]); + } + + @Test + public void callback_respondsWithCameraAccessDenied() { + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult( + 9796, null, new int[] {PackageManager.PERMISSION_DENIED}); + + verify(fakeResultCallback) + .onResult("CameraAccessDenied", "Camera access permission was denied."); + } + + @Test + public void callback_respondsWithAudioAccessDenied() { + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult( + 9796, + null, + new int[] {PackageManager.PERMISSION_GRANTED, PackageManager.PERMISSION_DENIED}); + + verify(fakeResultCallback).onResult("AudioAccessDenied", "Audio access permission was denied."); + } + + @Test + public void callback_doesNotRespond() { + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult( + 9796, + null, + new int[] {PackageManager.PERMISSION_GRANTED, PackageManager.PERMISSION_GRANTED}); + + verify(fakeResultCallback, never()) + .onResult("CameraAccessDenied", "Camera access permission was denied."); + verify(fakeResultCallback, never()) + .onResult("AudioAccessDenied", "Audio access permission was denied."); + } + + @Test + public void callback_respondsWithCameraAccessDeniedWhenEmptyResult() { + // Handles the case where the grantResults array is empty + + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult(9796, null, new int[] {}); + + verify(fakeResultCallback) + .onResult("CameraAccessDenied", "Camera access permission was denied."); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java new file mode 100644 index 000000000000..e2135b3945b0 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import androidx.camera.core.Camera; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class CameraTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public Camera camera; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.open(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.close(); + } + + @Test + public void flutterApiCreateTest() { + final CameraFlutterApiImpl spyFlutterApi = + spy(new CameraFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); + + spyFlutterApi.create(camera, reply -> {}); + + final long identifier = + Objects.requireNonNull(testInstanceManager.getIdentifierForStrongReference(camera)); + verify(spyFlutterApi).create(eq(identifier), any()); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java new file mode 100644 index 000000000000..1e2bfba714c7 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java @@ -0,0 +1,313 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.provider.Settings; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; +import io.flutter.plugins.camerax.DeviceOrientationManager.DeviceOrientationChangeCallback; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; + +public class DeviceOrientationManagerTest { + private Activity mockActivity; + private DeviceOrientationChangeCallback mockDeviceOrientationChangeCallback; + private WindowManager mockWindowManager; + private Display mockDisplay; + private DeviceOrientationManager deviceOrientationManager; + + @Before + @SuppressWarnings("deprecation") + public void before() { + mockActivity = mock(Activity.class); + mockDisplay = mock(Display.class); + mockWindowManager = mock(WindowManager.class); + mockDeviceOrientationChangeCallback = mock(DeviceOrientationChangeCallback.class); + + when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager); + when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay); + + deviceOrientationManager = + new DeviceOrientationManager(mockActivity, false, 0, mockDeviceOrientationChangeCallback); + } + + @Test + public void getVideoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() { + int degreesPortraitUp = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(0, degreesPortraitUp); + assertEquals(270, degreesLandscapeLeft); + assertEquals(180, degreesPortraitDown); + assertEquals(90, degreesLandscapeRight); + } + + @Test + public void getVideoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() { + DeviceOrientationManager orientationManager = + new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback); + + int degreesPortraitUp = orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(90, degreesPortraitUp); + assertEquals(0, degreesLandscapeLeft); + assertEquals(270, degreesPortraitDown); + assertEquals(180, degreesLandscapeRight); + } + + @Test + public void getVideoOrientation_fallbackToPortraitSensorOrientationWhenOrientationIsNull() { + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + + int degrees = deviceOrientationManager.getVideoOrientation(null); + + assertEquals(0, degrees); + } + + @Test + public void getVideoOrientation_fallbackToLandscapeSensorOrientationWhenOrientationIsNull() { + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + + DeviceOrientationManager orientationManager = + new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback); + + int degrees = orientationManager.getVideoOrientation(null); + + assertEquals(0, degrees); + } + + @Test + public void getPhotoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() { + int degreesPortraitUp = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(0, degreesPortraitUp); + assertEquals(90, degreesLandscapeRight); + assertEquals(180, degreesPortraitDown); + assertEquals(270, degreesLandscapeLeft); + } + + @Test + public void getPhotoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() { + DeviceOrientationManager orientationManager = + new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback); + + int degreesPortraitUp = orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(90, degreesPortraitUp); + assertEquals(180, degreesLandscapeRight); + assertEquals(270, degreesPortraitDown); + assertEquals(0, degreesLandscapeLeft); + } + + @Test + public void getPhotoOrientation_shouldFallbackToCurrentOrientationWhenOrientationIsNull() { + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + + int degrees = deviceOrientationManager.getPhotoOrientation(null); + + assertEquals(270, degrees); + } + + @Test + public void handleUIOrientationChange_shouldSendMessageWhenSensorAccessIsAllowed() { + try (MockedStatic mockedSystem = mockStatic(Settings.System.class)) { + mockedSystem + .when( + () -> + Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0))) + .thenReturn(0); + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + + deviceOrientationManager.handleUIOrientationChange(); + } + + verify(mockDeviceOrientationChangeCallback, times(1)) + .onChange(DeviceOrientation.LANDSCAPE_LEFT); + } + + @Test + public void handleOrientationChange_shouldSendMessageWhenOrientationIsUpdated() { + DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP; + DeviceOrientation newOrientation = DeviceOrientation.LANDSCAPE_LEFT; + + DeviceOrientationManager.handleOrientationChange( + newOrientation, previousOrientation, mockDeviceOrientationChangeCallback); + + verify(mockDeviceOrientationChangeCallback, times(1)).onChange(newOrientation); + } + + @Test + public void handleOrientationChange_shouldNotSendMessageWhenOrientationIsNotUpdated() { + DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP; + DeviceOrientation newOrientation = DeviceOrientation.PORTRAIT_UP; + + DeviceOrientationManager.handleOrientationChange( + newOrientation, previousOrientation, mockDeviceOrientationChangeCallback); + + verify(mockDeviceOrientationChangeCallback, never()).onChange(any()); + } + + @Test + public void getUIOrientation() { + // Orientation portrait and rotation of 0 should translate to "PORTRAIT_UP". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + DeviceOrientation uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); + + // Orientation portrait and rotation of 90 should translate to "PORTRAIT_UP". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_90); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); + + // Orientation portrait and rotation of 180 should translate to "PORTRAIT_DOWN". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_180); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_DOWN, uiOrientation); + + // Orientation portrait and rotation of 270 should translate to "PORTRAIT_DOWN". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_270); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_DOWN, uiOrientation); + + // Orientation landscape and rotation of 0 should translate to "LANDSCAPE_LEFT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_LEFT, uiOrientation); + + // Orientation landscape and rotation of 90 should translate to "LANDSCAPE_LEFT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_90); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_LEFT, uiOrientation); + + // Orientation landscape and rotation of 180 should translate to "LANDSCAPE_RIGHT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_180); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, uiOrientation); + + // Orientation landscape and rotation of 270 should translate to "LANDSCAPE_RIGHT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_270); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, uiOrientation); + + // Orientation undefined should default to "PORTRAIT_UP". + setUpUIOrientationMocks(Configuration.ORIENTATION_UNDEFINED, Surface.ROTATION_0); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); + } + + @Test + public void getDeviceDefaultOrientation() { + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + int orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_180); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_90); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_270); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_180); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_90); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_270); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + } + + @Test + public void calculateSensorOrientation() { + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + DeviceOrientation orientation = deviceOrientationManager.calculateSensorOrientation(0); + assertEquals(DeviceOrientation.PORTRAIT_UP, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + orientation = deviceOrientationManager.calculateSensorOrientation(90); + assertEquals(DeviceOrientation.LANDSCAPE_LEFT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + orientation = deviceOrientationManager.calculateSensorOrientation(180); + assertEquals(DeviceOrientation.PORTRAIT_DOWN, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + orientation = deviceOrientationManager.calculateSensorOrientation(270); + assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, orientation); + } + + private void setUpUIOrientationMocks(int orientation, int rotation) { + Resources mockResources = mock(Resources.class); + Configuration mockConfiguration = mock(Configuration.class); + + when(mockDisplay.getRotation()).thenReturn(rotation); + + mockConfiguration.orientation = orientation; + when(mockActivity.getResources()).thenReturn(mockResources); + when(mockResources.getConfiguration()).thenReturn(mockConfiguration); + } + + @Test + public void getDisplayTest() { + Display display = deviceOrientationManager.getDisplay(); + + assertEquals(mockDisplay, display); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java index 3878e05a40e8..e2e012dc35fb 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java @@ -5,6 +5,7 @@ package io.flutter.plugins.camerax; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -40,6 +41,20 @@ public void addHostCreatedInstance() { instanceManager.close(); } + @Test + public void addHostCreatedInstance_createsSameInstanceTwice() { + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + + final Object object = new Object(); + long firstIdentifier = instanceManager.addHostCreatedInstance(object); + long secondIdentifier = instanceManager.addHostCreatedInstance(object); + + assertNotEquals(firstIdentifier, secondIdentifier); + assertTrue(instanceManager.containsInstance(object)); + + instanceManager.close(); + } + @Test public void remove() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java index 5008e4ef34b0..47b4ed6ad26d 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java @@ -13,8 +13,12 @@ import static org.mockito.Mockito.when; import android.content.Context; +import androidx.camera.core.Camera; import androidx.camera.core.CameraInfo; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.UseCase; import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.lifecycle.LifecycleOwner; import androidx.test.core.app.ApplicationProvider; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -99,6 +103,58 @@ public void getAvailableCameraInfosTest() { verify(processCameraProvider).getAvailableCameraInfos(); } + @Test + public void bindToLifecycleTest() { + final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = + new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); + final Camera mockCamera = mock(Camera.class); + final CameraSelector mockCameraSelector = mock(CameraSelector.class); + final UseCase mockUseCase = mock(UseCase.class); + UseCase[] mockUseCases = new UseCase[] {mockUseCase}; + + LifecycleOwner mockLifecycleOwner = mock(LifecycleOwner.class); + processCameraProviderHostApi.setLifecycleOwner(mockLifecycleOwner); + + testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); + testInstanceManager.addDartCreatedInstance(mockCameraSelector, 1); + testInstanceManager.addDartCreatedInstance(mockUseCase, 2); + testInstanceManager.addDartCreatedInstance(mockCamera, 3); + + when(processCameraProvider.bindToLifecycle( + mockLifecycleOwner, mockCameraSelector, mockUseCases)) + .thenReturn(mockCamera); + + assertEquals( + processCameraProviderHostApi.bindToLifecycle(0L, 1L, Arrays.asList(2L)), Long.valueOf(3)); + verify(processCameraProvider) + .bindToLifecycle(mockLifecycleOwner, mockCameraSelector, mockUseCases); + } + + @Test + public void unbindTest() { + final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = + new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); + final UseCase mockUseCase = mock(UseCase.class); + UseCase[] mockUseCases = new UseCase[] {mockUseCase}; + + testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); + testInstanceManager.addDartCreatedInstance(mockUseCase, 1); + + processCameraProviderHostApi.unbind(0L, Arrays.asList(1L)); + verify(processCameraProvider).unbind(mockUseCases); + } + + @Test + public void unbindAllTest() { + final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = + new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); + + testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); + + processCameraProviderHostApi.unbindAll(0L); + verify(processCameraProvider).unbindAll(); + } + @Test public void flutterApiCreateTest() { final ProcessCameraProviderFlutterApiImpl spyFlutterApi = diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java new file mode 100644 index 000000000000..d90c2633271c --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java @@ -0,0 +1,137 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry; +import io.flutter.plugins.camerax.CameraPermissionsManager.ResultCallback; +import io.flutter.plugins.camerax.DeviceOrientationManager.DeviceOrientationChangeCallback; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraPermissionsErrorData; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi.Reply; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class SystemServicesTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public InstanceManager mockInstanceManager; + + @Test + public void requestCameraPermissionsTest() { + final SystemServicesHostApiImpl systemServicesHostApi = + new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager); + final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); + final CameraPermissionsManager mockCameraPermissionsManager = + mock(CameraPermissionsManager.class); + final Activity mockActivity = mock(Activity.class); + final PermissionsRegistry mockPermissionsRegistry = mock(PermissionsRegistry.class); + final Result mockResult = mock(Result.class); + final Boolean enableAudio = false; + + systemServicesHostApi.cameraXProxy = mockCameraXProxy; + systemServicesHostApi.setActivity(mockActivity); + systemServicesHostApi.setPermissionsRegistry(mockPermissionsRegistry); + when(mockCameraXProxy.createCameraPermissionsManager()) + .thenReturn(mockCameraPermissionsManager); + + final ArgumentCaptor resultCallbackCaptor = + ArgumentCaptor.forClass(ResultCallback.class); + + systemServicesHostApi.requestCameraPermissions(enableAudio, mockResult); + + // Test camera permissions are requested. + verify(mockCameraPermissionsManager) + .requestPermissions( + eq(mockActivity), + eq(mockPermissionsRegistry), + eq(enableAudio), + resultCallbackCaptor.capture()); + + ResultCallback resultCallback = (ResultCallback) resultCallbackCaptor.getValue(); + + // Test no error data is sent upon permissions request success. + resultCallback.onResult(null, null); + verify(mockResult).success(null); + + // Test expected error data is sent upon permissions request failure. + final String testErrorCode = "TestErrorCode"; + final String testErrorDescription = "Test error description."; + + final ArgumentCaptor cameraPermissionsErrorDataCaptor = + ArgumentCaptor.forClass(CameraPermissionsErrorData.class); + + resultCallback.onResult(testErrorCode, testErrorDescription); + verify(mockResult, times(2)).success(cameraPermissionsErrorDataCaptor.capture()); + + CameraPermissionsErrorData cameraPermissionsErrorData = + cameraPermissionsErrorDataCaptor.getValue(); + assertEquals(cameraPermissionsErrorData.getErrorCode(), testErrorCode); + assertEquals(cameraPermissionsErrorData.getDescription(), testErrorDescription); + } + + @Test + public void deviceOrientationChangeTest() { + final SystemServicesHostApiImpl systemServicesHostApi = + new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager); + final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); + final Activity mockActivity = mock(Activity.class); + final DeviceOrientationManager mockDeviceOrientationManager = + mock(DeviceOrientationManager.class); + final Boolean isFrontFacing = true; + final int sensorOrientation = 90; + + SystemServicesFlutterApiImpl systemServicesFlutterApi = + mock(SystemServicesFlutterApiImpl.class); + systemServicesHostApi.systemServicesFlutterApi = systemServicesFlutterApi; + + systemServicesHostApi.cameraXProxy = mockCameraXProxy; + systemServicesHostApi.setActivity(mockActivity); + when(mockCameraXProxy.createDeviceOrientationManager( + eq(mockActivity), + eq(isFrontFacing), + eq(sensorOrientation), + any(DeviceOrientationChangeCallback.class))) + .thenReturn(mockDeviceOrientationManager); + + final ArgumentCaptor deviceOrientationChangeCallbackCaptor = + ArgumentCaptor.forClass(DeviceOrientationChangeCallback.class); + + systemServicesHostApi.startListeningForDeviceOrientationChange( + isFrontFacing, Long.valueOf(sensorOrientation)); + + // Test callback method defined in Flutter API is called when device orientation changes. + verify(mockCameraXProxy) + .createDeviceOrientationManager( + eq(mockActivity), + eq(isFrontFacing), + eq(sensorOrientation), + deviceOrientationChangeCallbackCaptor.capture()); + DeviceOrientationChangeCallback deviceOrientationChangeCallback = + deviceOrientationChangeCallbackCaptor.getValue(); + + deviceOrientationChangeCallback.onChange(DeviceOrientation.PORTRAIT_DOWN); + verify(systemServicesFlutterApi) + .onDeviceOrientationChanged(eq("PORTRAIT_DOWN"), any(Reply.class)); + + // Test that the DeviceOrientationManager starts listening for device orientation changes. + verify(mockDeviceOrientationManager).start(); + } +} diff --git a/packages/camera/camera_android_camerax/example/android/build.gradle b/packages/camera/camera_android_camerax/example/android/build.gradle index 20411f5f31a9..8640e4de86a1 100644 --- a/packages/camera/camera_android_camerax/example/android/build.gradle +++ b/packages/camera/camera_android_camerax/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index 9c6564a06c08..0a1b3ce3b285 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -2,20 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'java_object.dart'; import 'process_camera_provider.dart'; +import 'system_services.dart'; /// Handles initialization of Flutter APIs for the Android CameraX library. class AndroidCameraXCameraFlutterApis { /// Creates a [AndroidCameraXCameraFlutterApis]. AndroidCameraXCameraFlutterApis({ JavaObjectFlutterApiImpl? javaObjectFlutterApi, + CameraFlutterApiImpl? cameraFlutterApi, CameraInfoFlutterApiImpl? cameraInfoFlutterApi, CameraSelectorFlutterApiImpl? cameraSelectorFlutterApi, ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApi, + SystemServicesFlutterApiImpl? systemServicesFlutterApi, }) { this.javaObjectFlutterApi = javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); @@ -25,6 +29,9 @@ class AndroidCameraXCameraFlutterApis { cameraSelectorFlutterApi ?? CameraSelectorFlutterApiImpl(); this.processCameraProviderFlutterApi = processCameraProviderFlutterApi ?? ProcessCameraProviderFlutterApiImpl(); + this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl(); + this.systemServicesFlutterApi = + systemServicesFlutterApi ?? SystemServicesFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -48,6 +55,12 @@ class AndroidCameraXCameraFlutterApis { late final ProcessCameraProviderFlutterApiImpl processCameraProviderFlutterApi; + /// Flutter Api for [Camera]. + late final CameraFlutterApiImpl cameraFlutterApi; + + /// Flutter Api for [SystemServices]. + late final SystemServicesFlutterApiImpl systemServicesFlutterApi; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -55,6 +68,8 @@ class AndroidCameraXCameraFlutterApis { CameraInfoFlutterApi.setup(cameraInfoFlutterApi); CameraSelectorFlutterApi.setup(cameraSelectorFlutterApi); ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi); + CameraFlutterApi.setup(cameraFlutterApi); + SystemServicesFlutterApi.setup(systemServicesFlutterApi); _haveBeenSetUp = true; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera.dart b/packages/camera/camera_android_camerax/lib/src/camera.dart new file mode 100644 index 000000000000..24ff30540b28 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart' show BinaryMessenger; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// The interface used to control the flow of data of use cases, control the +/// camera, and publich the state of the camera. +/// +/// See https://developer.android.com/reference/androidx/camera/core/Camera. +class Camera extends JavaObject { + /// Constructs a [Camera] that is not automatically attached to a native object. + Camera.detached({super.binaryMessenger, super.instanceManager}) + : super.detached() { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } +} + +/// Flutter API implementation of [Camera]. +class CameraFlutterApiImpl implements CameraFlutterApi { + /// Constructs a [CameraSelectorFlutterApiImpl]. + CameraFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create(int identifier) { + instanceManager.addHostCreatedInstance( + Camera.detached( + binaryMessenger: binaryMessenger, instanceManager: instanceManager), + identifier, + onCopy: (Camera original) { + return Camera.detached( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + }, + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera_info.dart index d03426f40027..8c2c7bcf0aec 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_info.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart' show BinaryMessenger; import 'android_camera_camerax_flutter_api_impls.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart index 094147f208fe..43a1dabd6906 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; import 'android_camera_camerax_flutter_api_impls.dart'; import 'camera_info.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart similarity index 58% rename from packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart rename to packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index c0b052378def..6d8869968f41 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -10,6 +10,31 @@ import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; +class CameraPermissionsErrorData { + CameraPermissionsErrorData({ + required this.errorCode, + required this.description, + }); + + String errorCode; + String description; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['errorCode'] = errorCode; + pigeonMap['description'] = description; + return pigeonMap; + } + + static CameraPermissionsErrorData decode(Object message) { + final Map pigeonMap = message as Map; + return CameraPermissionsErrorData( + errorCode: pigeonMap['errorCode']! as String, + description: pigeonMap['description']! as String, + ); + } +} + class _JavaObjectHostApiCodec extends StandardMessageCodec { const _JavaObjectHostApiCodec(); } @@ -338,6 +363,89 @@ class ProcessCameraProviderHostApi { return (replyMap['result'] as List?)!.cast(); } } + + Future bindToLifecycle(int arg_identifier, + int arg_cameraSelectorIdentifier, List arg_useCaseIds) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel.send([ + arg_identifier, + arg_cameraSelectorIdentifier, + arg_useCaseIds + ]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else if (replyMap['result'] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyMap['result'] as int?)!; + } + } + + Future unbind(int arg_identifier, List arg_useCaseIds) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier, arg_useCaseIds]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future unbindAll(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } } class _ProcessCameraProviderFlutterApiCodec extends StandardMessageCodec { @@ -372,3 +480,181 @@ abstract class ProcessCameraProviderFlutterApi { } } } + +class _CameraFlutterApiCodec extends StandardMessageCodec { + const _CameraFlutterApiCodec(); +} + +abstract class CameraFlutterApi { + static const MessageCodec codec = _CameraFlutterApiCodec(); + + void create(int identifier); + static void setup(CameraFlutterApi? api, {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CameraFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CameraFlutterApi.create was null, expected non-null int.'); + api.create(arg_identifier!); + return; + }); + } + } + } +} + +class _SystemServicesHostApiCodec extends StandardMessageCodec { + const _SystemServicesHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is CameraPermissionsErrorData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return CameraPermissionsErrorData.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +class SystemServicesHostApi { + /// Constructor for [SystemServicesHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + SystemServicesHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _SystemServicesHostApiCodec(); + + Future requestCameraPermissions( + bool arg_enableAudio) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_enableAudio]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return (replyMap['result'] as CameraPermissionsErrorData?); + } + } + + Future startListeningForDeviceOrientationChange( + bool arg_isFrontFacing, int arg_sensorOrientation) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_isFrontFacing, arg_sensorOrientation]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future stopListeningForDeviceOrientationChange() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send(null) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } +} + +class _SystemServicesFlutterApiCodec extends StandardMessageCodec { + const _SystemServicesFlutterApiCodec(); +} + +abstract class SystemServicesFlutterApi { + static const MessageCodec codec = _SystemServicesFlutterApiCodec(); + + void onDeviceOrientationChanged(String orientation); + static void setup(SystemServicesFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null.'); + final List args = (message as List?)!; + final String? arg_orientation = (args[0] as String?); + assert(arg_orientation != null, + 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null, expected non-null String.'); + api.onDeviceOrientationChanged(arg_orientation!); + return; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/instance_manager.dart b/packages/camera/camera_android_camerax/lib/src/instance_manager.dart index dd48610c8b56..8c6081c855ba 100644 --- a/packages/camera/camera_android_camerax/lib/src/instance_manager.dart +++ b/packages/camera/camera_android_camerax/lib/src/instance_manager.dart @@ -124,22 +124,26 @@ class InstanceManager { /// This method also expects the host `InstanceManager` to have a strong /// reference to the instance the identifier is associated with. T? getInstanceWithWeakReference(int identifier) { - final Object? weakInstance = _weakInstances[identifier]?.target; + final T? weakInstance = _weakInstances[identifier]?.target as T?; if (weakInstance == null) { - final Object? strongInstance = _strongInstances[identifier]; + final T? strongInstance = _strongInstances[identifier] as T?; if (strongInstance != null) { - final Object copy = - _copyCallbacks[identifier]!(strongInstance)! as Object; + // This cast is safe since it matches the argument type for + // _addInstanceWithIdentifier, which is the only place _copyCallbacks + // is populated. + final T Function(T) copyCallback = + _copyCallbacks[identifier]! as T Function(T); + final T copy = copyCallback(strongInstance); _identifiers[copy] = identifier; - _weakInstances[identifier] = WeakReference(copy); + _weakInstances[identifier] = WeakReference(copy); _finalizer.attach(copy, identifier, detach: copy); - return copy as T; + return copy; } - return strongInstance as T?; + return strongInstance; } - return weakInstance as T; + return weakInstance; } /// Retrieves the identifier associated with instance. diff --git a/packages/camera/camera_android_camerax/lib/src/java_object.dart b/packages/camera/camera_android_camerax/lib/src/java_object.dart index 36a29ed0517b..f6127d4a8106 100644 --- a/packages/camera/camera_android_camerax/lib/src/java_object.dart +++ b/packages/camera/camera_android_camerax/lib/src/java_object.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/services.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; /// Root of the Java class hierarchy. diff --git a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart index 5a67fa7e4dc3..ed9e820a1fa0 100644 --- a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart +++ b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart @@ -5,10 +5,13 @@ import 'package:flutter/services.dart'; import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camera.dart'; import 'camera_info.dart'; -import 'camerax_library.pigeon.dart'; +import 'camera_selector.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'use_case.dart'; /// Provides an object to manage the camera. /// @@ -42,6 +45,25 @@ class ProcessCameraProvider extends JavaObject { Future> getAvailableCameraInfos() { return _api.getAvailableCameraInfosFromInstances(this); } + + /// Binds the specified [UseCase]s to the lifecycle of the camera that it + /// returns. + Future bindToLifecycle( + CameraSelector cameraSelector, List useCases) { + return _api.bindToLifecycleFromInstances(this, cameraSelector, useCases); + } + + /// Unbinds specified [UseCase]s from the lifecycle of the camera that this + /// instance tracks. + void unbind(List useCases) { + _api.unbindFromInstances(this, useCases); + } + + /// Unbinds all previously bound [UseCase]s from the lifecycle of the camera + /// that this tracks. + void unbindAll() { + _api.unbindAllFromInstances(this); + } } /// Host API implementation of [ProcessCameraProvider]. @@ -69,22 +91,71 @@ class ProcessCameraProviderHostApiImpl extends ProcessCameraProviderHostApi { as ProcessCameraProvider; } + /// Gets identifier that the [instanceManager] has set for + /// the [ProcessCameraProvider] instance. + int getProcessCameraProviderIdentifier(ProcessCameraProvider instance) { + final int? identifier = instanceManager.getIdentifier(instance); + + assert(identifier != null, + 'No ProcessCameraProvider has the identifer of that which was requested.'); + return identifier!; + } + /// Retrives the list of CameraInfos corresponding to the available cameras. Future> getAvailableCameraInfosFromInstances( ProcessCameraProvider instance) async { - int? identifier = instanceManager.getIdentifier(instance); - identifier ??= instanceManager.addDartCreatedInstance(instance, - onCopy: (ProcessCameraProvider original) { - return ProcessCameraProvider.detached( - binaryMessenger: binaryMessenger, instanceManager: instanceManager); - }); - + final int identifier = getProcessCameraProviderIdentifier(instance); final List cameraInfos = await getAvailableCameraInfos(identifier); return cameraInfos .map((int? id) => instanceManager.getInstanceWithWeakReference(id!)! as CameraInfo) .toList(); } + + /// Binds the specified [UseCase]s to the lifecycle of the camera which + /// the provided [ProcessCameraProvider] instance tracks. + /// + /// The instance of the camera whose lifecycle the [UseCase]s are bound to + /// is returned. + Future bindToLifecycleFromInstances( + ProcessCameraProvider instance, + CameraSelector cameraSelector, + List useCases, + ) async { + final int identifier = getProcessCameraProviderIdentifier(instance); + final List useCaseIds = useCases + .map((UseCase useCase) => instanceManager.getIdentifier(useCase)!) + .toList(); + + final int cameraIdentifier = await bindToLifecycle( + identifier, + instanceManager.getIdentifier(cameraSelector)!, + useCaseIds, + ); + return instanceManager.getInstanceWithWeakReference(cameraIdentifier)! + as Camera; + } + + /// Unbinds specified [UseCase]s from the lifecycle of the camera which the + /// provided [ProcessCameraProvider] instance tracks. + void unbindFromInstances( + ProcessCameraProvider instance, + List useCases, + ) { + final int identifier = getProcessCameraProviderIdentifier(instance); + final List useCaseIds = useCases + .map((UseCase useCase) => instanceManager.getIdentifier(useCase)!) + .toList(); + + unbind(identifier, useCaseIds); + } + + /// Unbinds all previously bound [UseCase]s from the lifecycle of the camera + /// which the provided [ProcessCameraProvider] instance tracks. + void unbindAllFromInstances(ProcessCameraProvider instance) { + final int identifier = getProcessCameraProviderIdentifier(instance); + unbindAll(identifier); + } } /// Flutter API Implementation of [ProcessCameraProvider]. diff --git a/packages/camera/camera_android_camerax/lib/src/system_services.dart b/packages/camera/camera_android_camerax/lib/src/system_services.dart new file mode 100644 index 000000000000..bc6477e0dcb8 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -0,0 +1,137 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:camera_platform_interface/camera_platform_interface.dart' + show CameraException, DeviceOrientationChangedEvent; +import 'package:flutter/services.dart'; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.g.dart'; + +// Ignoring lint indicating this class only contains static members +// as this class is a wrapper for various Android system services. +// ignore_for_file: avoid_classes_with_only_static_members + +/// Utility class that offers access to Android system services needed for +/// camera usage. +class SystemServices { + /// Stream that emits the device orientation whenever it is changed. + /// + /// Values may start being added to the stream once + /// `startListeningForDeviceOrientationChange(...)` is called. + static final StreamController + deviceOrientationChangedStreamController = + StreamController.broadcast(); + + /// Requests permission to access the camera and audio if specified. + static Future requestCameraPermissions(bool enableAudio, + {BinaryMessenger? binaryMessenger}) { + final SystemServicesHostApiImpl api = + SystemServicesHostApiImpl(binaryMessenger: binaryMessenger); + + return api.sendCameraPermissionsRequest(enableAudio); + } + + /// Requests that [deviceOrientationChangedStreamController] start + /// emitting values for any change in device orientation. + static void startListeningForDeviceOrientationChange( + bool isFrontFacing, int sensorOrientation, + {BinaryMessenger? binaryMessenger}) { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + final SystemServicesHostApi api = + SystemServicesHostApi(binaryMessenger: binaryMessenger); + + api.startListeningForDeviceOrientationChange( + isFrontFacing, sensorOrientation); + } + + /// Stops the [deviceOrientationChangedStreamController] from emitting values + /// for changes in device orientation. + static void stopListeningForDeviceOrientationChange( + {BinaryMessenger? binaryMessenger}) { + final SystemServicesHostApi api = + SystemServicesHostApi(binaryMessenger: binaryMessenger); + + api.stopListeningForDeviceOrientationChange(); + } +} + +/// Host API implementation of [SystemServices]. +class SystemServicesHostApiImpl extends SystemServicesHostApi { + /// Creates a [SystemServicesHostApiImpl]. + SystemServicesHostApiImpl({this.binaryMessenger}) + : super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Requests permission to access the camera and audio if specified. + /// + /// Will complete normally if permissions are successfully granted; otherwise, + /// will throw a [CameraException]. + Future sendCameraPermissionsRequest(bool enableAudio) async { + final CameraPermissionsErrorData? error = + await requestCameraPermissions(enableAudio); + + if (error != null) { + throw CameraException( + error.errorCode, + error.description, + ); + } + } +} + +/// Flutter API implementation of [SystemServices]. +class SystemServicesFlutterApiImpl implements SystemServicesFlutterApi { + /// Constructs a [SystemServicesFlutterApiImpl]. + SystemServicesFlutterApiImpl({ + this.binaryMessenger, + }); + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Callback method for any changes in device orientation. + /// + /// Will only be called if + /// `SystemServices.startListeningForDeviceOrientationChange(...)` was called + /// to start listening for device orientation updates. + @override + void onDeviceOrientationChanged(String orientation) { + final DeviceOrientation deviceOrientation = + deserializeDeviceOrientation(orientation); + if (deviceOrientation == null) { + return; + } + SystemServices.deviceOrientationChangedStreamController + .add(DeviceOrientationChangedEvent(deviceOrientation)); + } + + /// Deserializes device orientation in [String] format into a + /// [DeviceOrientation]. + DeviceOrientation deserializeDeviceOrientation(String orientation) { + switch (orientation) { + case 'LANDSCAPE_LEFT': + return DeviceOrientation.landscapeLeft; + case 'LANDSCAPE_RIGHT': + return DeviceOrientation.landscapeRight; + case 'PORTRAIT_DOWN': + return DeviceOrientation.portraitDown; + case 'PORTRAIT_UP': + return DeviceOrientation.portraitUp; + default: + throw ArgumentError( + '"$orientation" is not a valid DeviceOrientation value'); + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/use_case.dart b/packages/camera/camera_android_camerax/lib/src/use_case.dart new file mode 100644 index 000000000000..f8910d9c5347 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/use_case.dart @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'java_object.dart'; + +/// An object representing the different functionalitites of the camera. +/// +/// See https://developer.android.com/reference/androidx/camera/core/UseCase. +class UseCase extends JavaObject { + /// Creates a detached [UseCase]. + UseCase.detached({super.binaryMessenger, super.instanceManager}) + : super.detached(); +} diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 4d7d96910246..7fce6ce329fd 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -6,8 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( - dartOut: 'lib/src/camerax_library.pigeon.dart', - dartTestOut: 'test/test_camerax_library.pigeon.dart', + dartOut: 'lib/src/camerax_library.g.dart', + dartTestOut: 'test/test_camerax_library.g.dart', dartOptions: DartOptions(copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', @@ -26,6 +26,16 @@ import 'package:pigeon/pigeon.dart'; ), ), ) +class CameraPermissionsErrorData { + CameraPermissionsErrorData({ + required this.errorCode, + required this.description, + }); + + String errorCode; + String description; +} + @HostApi(dartHostTestHandler: 'TestJavaObjectHostApi') abstract class JavaObjectHostApi { void dispose(int identifier); @@ -64,9 +74,37 @@ abstract class ProcessCameraProviderHostApi { int getInstance(); List getAvailableCameraInfos(int identifier); + + int bindToLifecycle( + int identifier, int cameraSelectorIdentifier, List useCaseIds); + + void unbind(int identifier, List useCaseIds); + + void unbindAll(int identifier); } @FlutterApi() abstract class ProcessCameraProviderFlutterApi { void create(int identifier); } + +@FlutterApi() +abstract class CameraFlutterApi { + void create(int identifier); +} + +@HostApi(dartHostTestHandler: 'TestSystemServicesHostApi') +abstract class SystemServicesHostApi { + @async + CameraPermissionsErrorData? requestCameraPermissions(bool enableAudio); + + void startListeningForDeviceOrientationChange( + bool isFrontFacing, int sensorOrientation); + + void stopListeningForDeviceOrientationChange(); +} + +@FlutterApi() +abstract class SystemServicesFlutterApi { + void onDeviceOrientationChanged(String orientation); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 9873db1a0121..7f81ecbd4f71 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: camera_platform_interface: ^2.2.0 flutter: sdk: flutter + stream_transform: ^2.1.0 dev_dependencies: build_runner: ^2.1.4 diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera_info_test.dart index eda822b33f73..852c799ebfbe 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.dart @@ -3,14 +3,14 @@ // found in the LICENSE file. import 'package:camera_android_camerax/src/camera_info.dart'; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'camera_info_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestCameraInfoHostApi]) void main() { diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart index e1f1e3ca9e9b..5e558a8226b6 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart @@ -1,11 +1,11 @@ -// Mocks generated by Mockito 5.3.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/camera_info_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -29,6 +29,10 @@ class MockTestCameraInfoHostApi extends _i1.Mock @override int getSensorRotationDegrees(int? identifier) => (super.noSuchMethod( - Invocation.method(#getSensorRotationDegrees, [identifier]), - returnValue: 0) as int); + Invocation.method( + #getSensorRotationDegrees, + [identifier], + ), + returnValue: 0, + ) as int); } diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.dart index c4ccd6262376..54f7864fb85f 100644 --- a/packages/camera/camera_android_camerax/test/camera_selector_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_selector_test.dart @@ -4,14 +4,14 @@ import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'camera_selector_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestCameraSelectorHostApi]) void main() { diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart index 456db1eaf822..31dce5177e2d 100644 --- a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart @@ -1,11 +1,11 @@ -// Mocks generated by Mockito 5.3.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/camera_selector_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -28,11 +28,33 @@ class MockTestCameraSelectorHostApi extends _i1.Mock } @override - void create(int? identifier, int? lensFacing) => - super.noSuchMethod(Invocation.method(#create, [identifier, lensFacing]), - returnValueForMissingStub: null); + void create( + int? identifier, + int? lensFacing, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + lensFacing, + ], + ), + returnValueForMissingStub: null, + ); @override - List filter(int? identifier, List? cameraInfoIds) => (super - .noSuchMethod(Invocation.method(#filter, [identifier, cameraInfoIds]), - returnValue: []) as List); + List filter( + int? identifier, + List? cameraInfoIds, + ) => + (super.noSuchMethod( + Invocation.method( + #filter, + [ + identifier, + cameraInfoIds, + ], + ), + returnValue: [], + ) as List); } diff --git a/packages/camera/camera_android_camerax/test/camera_test.dart b/packages/camera/camera_android_camerax/test/camera_test.dart new file mode 100644 index 000000000000..c2948282dcf1 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera_test.dart @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/camera.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Camera', () { + test('flutterApiCreateTest', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final CameraFlutterApiImpl flutterApi = CameraFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.create(0); + + expect(instanceManager.getInstanceWithWeakReference(0), isA()); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart index 65e7d00ddaea..548ac3e00d65 100644 --- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart +++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart @@ -2,15 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_selector.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:camera_android_camerax/src/process_camera_provider.dart'; +import 'package:camera_android_camerax/src/use_case.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'process_camera_provider_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestProcessCameraProviderHostApi]) void main() { @@ -78,6 +81,114 @@ void main() { verify(mockApi.getAvailableCameraInfos(0)); }); + test('bindToLifecycleTest', () async { + final MockTestProcessCameraProviderHostApi mockApi = + MockTestProcessCameraProviderHostApi(); + TestProcessCameraProviderHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ProcessCameraProvider processCameraProvider = + ProcessCameraProvider.detached( + instanceManager: instanceManager, + ); + final CameraSelector fakeCameraSelector = + CameraSelector.detached(instanceManager: instanceManager); + final UseCase fakeUseCase = + UseCase.detached(instanceManager: instanceManager); + final Camera fakeCamera = + Camera.detached(instanceManager: instanceManager); + + instanceManager.addHostCreatedInstance( + processCameraProvider, + 0, + onCopy: (_) => ProcessCameraProvider.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeCameraSelector, + 1, + onCopy: (_) => CameraSelector.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeUseCase, + 2, + onCopy: (_) => UseCase.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeCamera, + 3, + onCopy: (_) => Camera.detached(), + ); + + when(mockApi.bindToLifecycle(0, 1, [2])).thenReturn(3); + expect( + await processCameraProvider + .bindToLifecycle(fakeCameraSelector, [fakeUseCase]), + equals(fakeCamera)); + verify(mockApi.bindToLifecycle(0, 1, [2])); + }); + + test('unbindTest', () async { + final MockTestProcessCameraProviderHostApi mockApi = + MockTestProcessCameraProviderHostApi(); + TestProcessCameraProviderHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ProcessCameraProvider processCameraProvider = + ProcessCameraProvider.detached( + instanceManager: instanceManager, + ); + final UseCase fakeUseCase = + UseCase.detached(instanceManager: instanceManager); + + instanceManager.addHostCreatedInstance( + processCameraProvider, + 0, + onCopy: (_) => ProcessCameraProvider.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeUseCase, + 1, + onCopy: (_) => UseCase.detached(), + ); + + processCameraProvider.unbind([fakeUseCase]); + verify(mockApi.unbind(0, [1])); + }); + + test('unbindAllTest', () async { + final MockTestProcessCameraProviderHostApi mockApi = + MockTestProcessCameraProviderHostApi(); + TestProcessCameraProviderHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ProcessCameraProvider processCameraProvider = + ProcessCameraProvider.detached( + instanceManager: instanceManager, + ); + final UseCase fakeUseCase = + UseCase.detached(instanceManager: instanceManager); + + instanceManager.addHostCreatedInstance( + processCameraProvider, + 0, + onCopy: (_) => ProcessCameraProvider.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeUseCase, + 1, + onCopy: (_) => UseCase.detached(), + ); + + processCameraProvider.unbind([fakeUseCase]); + verify(mockApi.unbind(0, [1])); + }); + test('flutterApiCreateTest', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart index 9fcfe690c062..2ce4ab72fa57 100644 --- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/process_camera_provider_test.dart. // Do not manually edit this file. @@ -7,7 +7,7 @@ import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -30,11 +30,59 @@ class MockTestProcessCameraProviderHostApi extends _i1.Mock } @override - _i3.Future getInstance() => - (super.noSuchMethod(Invocation.method(#getInstance, []), - returnValue: _i3.Future.value(0)) as _i3.Future); + _i3.Future getInstance() => (super.noSuchMethod( + Invocation.method( + #getInstance, + [], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); @override List getAvailableCameraInfos(int? identifier) => (super.noSuchMethod( - Invocation.method(#getAvailableCameraInfos, [identifier]), - returnValue: []) as List); + Invocation.method( + #getAvailableCameraInfos, + [identifier], + ), + returnValue: [], + ) as List); + @override + int bindToLifecycle( + int? identifier, + int? cameraSelectorIdentifier, + List? useCaseIds, + ) => + (super.noSuchMethod( + Invocation.method( + #bindToLifecycle, + [ + identifier, + cameraSelectorIdentifier, + useCaseIds, + ], + ), + returnValue: 0, + ) as int); + @override + void unbind( + int? identifier, + List? useCaseIds, + ) => + super.noSuchMethod( + Invocation.method( + #unbind, + [ + identifier, + useCaseIds, + ], + ), + returnValueForMissingStub: null, + ); + @override + void unbindAll(int? identifier) => super.noSuchMethod( + Invocation.method( + #unbindAll, + [identifier], + ), + returnValueForMissingStub: null, + ); } diff --git a/packages/camera/camera_android_camerax/test/system_services_test.dart b/packages/camera/camera_android_camerax/test/system_services_test.dart new file mode 100644 index 000000000000..2d2cea6d9190 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/system_services_test.dart @@ -0,0 +1,101 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/camerax_library.g.dart' + show CameraPermissionsErrorData; +import 'package:camera_android_camerax/src/system_services.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart' + show CameraException, DeviceOrientationChangedEvent; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'system_services_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestSystemServicesHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('SystemServices', () { + tearDown(() => TestProcessCameraProviderHostApi.setup(null)); + + test( + 'requestCameraPermissionsFromInstance completes normally without errors test', + () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + + when(mockApi.requestCameraPermissions(true)) + .thenAnswer((_) async => null); + + await SystemServices.requestCameraPermissions(true); + verify(mockApi.requestCameraPermissions(true)); + }); + + test( + 'requestCameraPermissionsFromInstance throws CameraException if there was a request error', + () { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + final CameraPermissionsErrorData error = CameraPermissionsErrorData( + errorCode: 'Test error code', + description: 'Test error description', + ); + + when(mockApi.requestCameraPermissions(true)) + .thenAnswer((_) async => error); + + expect( + () async => SystemServices.requestCameraPermissions(true), + throwsA(isA() + .having((CameraException e) => e.code, 'code', 'Test error code') + .having((CameraException e) => e.description, 'description', + 'Test error description'))); + verify(mockApi.requestCameraPermissions(true)); + }); + + test('startListeningForDeviceOrientationChangeTest', () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + + SystemServices.startListeningForDeviceOrientationChange(true, 90); + verify(mockApi.startListeningForDeviceOrientationChange(true, 90)); + }); + + test('stopListeningForDeviceOrientationChangeTest', () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + + SystemServices.stopListeningForDeviceOrientationChange(); + verify(mockApi.stopListeningForDeviceOrientationChange()); + }); + + test('onDeviceOrientationChanged adds new orientation to stream', () { + SystemServices.deviceOrientationChangedStreamController.stream + .listen((DeviceOrientationChangedEvent event) { + expect(event.orientation, equals(DeviceOrientation.landscapeLeft)); + }); + SystemServicesFlutterApiImpl() + .onDeviceOrientationChanged('LANDSCAPE_LEFT'); + }); + + test( + 'onDeviceOrientationChanged throws error if new orientation is invalid', + () { + expect( + () => SystemServicesFlutterApiImpl() + .onDeviceOrientationChanged('FAKE_ORIENTATION'), + throwsA(isA().having( + (ArgumentError e) => e.message, + 'message', + '"FAKE_ORIENTATION" is not a valid DeviceOrientation value'))); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart new file mode 100644 index 000000000000..0963ffb26a2a --- /dev/null +++ b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart @@ -0,0 +1,66 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in camera_android_camerax/test/system_services_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestSystemServicesHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestSystemServicesHostApi extends _i1.Mock + implements _i2.TestSystemServicesHostApi { + MockTestSystemServicesHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future<_i4.CameraPermissionsErrorData?> requestCameraPermissions( + bool? enableAudio) => + (super.noSuchMethod( + Invocation.method( + #requestCameraPermissions, + [enableAudio], + ), + returnValue: _i3.Future<_i4.CameraPermissionsErrorData?>.value(), + ) as _i3.Future<_i4.CameraPermissionsErrorData?>); + @override + void startListeningForDeviceOrientationChange( + bool? isFrontFacing, + int? sensorOrientation, + ) => + super.noSuchMethod( + Invocation.method( + #startListeningForDeviceOrientationChange, + [ + isFrontFacing, + sensorOrientation, + ], + ), + returnValueForMissingStub: null, + ); + @override + void stopListeningForDeviceOrientationChange() => super.noSuchMethod( + Invocation.method( + #stopListeningForDeviceOrientationChange, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart similarity index 50% rename from packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart rename to packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 2196b73d7fdb..55f2c5e3e6a6 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; class _TestJavaObjectHostApiCodec extends StandardMessageCodec { const _TestJavaObjectHostApiCodec(); @@ -146,6 +146,10 @@ abstract class TestProcessCameraProviderHostApi { Future getInstance(); List getAvailableCameraInfos(int identifier); + int bindToLifecycle( + int identifier, int cameraSelectorIdentifier, List useCaseIds); + void unbind(int identifier, List useCaseIds); + void unbindAll(int identifier); static void setup(TestProcessCameraProviderHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -183,5 +187,172 @@ abstract class TestProcessCameraProviderHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null int.'); + final int? arg_cameraSelectorIdentifier = (args[1] as int?); + assert(arg_cameraSelectorIdentifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null int.'); + final List? arg_useCaseIds = + (args[2] as List?)?.cast(); + assert(arg_useCaseIds != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null List.'); + final int output = api.bindToLifecycle( + arg_identifier!, arg_cameraSelectorIdentifier!, arg_useCaseIds!); + return {'result': output}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null, expected non-null int.'); + final List? arg_useCaseIds = + (args[1] as List?)?.cast(); + assert(arg_useCaseIds != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null, expected non-null List.'); + api.unbind(arg_identifier!, arg_useCaseIds!); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null, expected non-null int.'); + api.unbindAll(arg_identifier!); + return {}; + }); + } + } + } +} + +class _TestSystemServicesHostApiCodec extends StandardMessageCodec { + const _TestSystemServicesHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is CameraPermissionsErrorData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return CameraPermissionsErrorData.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestSystemServicesHostApi { + static const MessageCodec codec = _TestSystemServicesHostApiCodec(); + + Future requestCameraPermissions( + bool enableAudio); + void startListeningForDeviceOrientationChange( + bool isFrontFacing, int sensorOrientation); + void stopListeningForDeviceOrientationChange(); + static void setup(TestSystemServicesHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions was null.'); + final List args = (message as List?)!; + final bool? arg_enableAudio = (args[0] as bool?); + assert(arg_enableAudio != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions was null, expected non-null bool.'); + final CameraPermissionsErrorData? output = + await api.requestCameraPermissions(arg_enableAudio!); + return {'result': output}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null.'); + final List args = (message as List?)!; + final bool? arg_isFrontFacing = (args[0] as bool?); + assert(arg_isFrontFacing != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null, expected non-null bool.'); + final int? arg_sensorOrientation = (args[1] as int?); + assert(arg_sensorOrientation != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null, expected non-null int.'); + api.startListeningForDeviceOrientationChange( + arg_isFrontFacing!, arg_sensorOrientation!); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + // ignore message + api.stopListeningForDeviceOrientationChange(); + return {}; + }); + } + } } } diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 9217f2633322..f0605b7914cc 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.9.11 + +* Adds back use of Optional type. +* Updates minimum Flutter version to 3.0. + +## 0.9.10+2 + +* Updates code for stricter lint checks. + +## 0.9.10+1 + +* Updates code for stricter lint checks. + ## 0.9.10 * Remove usage of deprecated quiver Optional type. diff --git a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart index 47c1f6f0415b..524186816aab 100644 --- a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart +++ b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; @@ -108,10 +109,10 @@ class CameraValue { bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, - DeviceOrientation? lockedCaptureOrientation, - DeviceOrientation? recordingOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, bool? isPreviewPaused, - DeviceOrientation? previewPauseOrientation, + Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -124,12 +125,16 @@ class CameraValue { exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation ?? this.lockedCaptureOrientation, - recordingOrientation: recordingOrientation ?? this.recordingOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - previewPauseOrientation: - previewPauseOrientation ?? this.previewPauseOrientation, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -257,14 +262,16 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, - previewPauseOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Resumes the current camera preview Future resumePreview() async { await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith(isPreviewPaused: false); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); } /// Captures an image and returns the file where it was saved. @@ -307,8 +314,8 @@ class CameraController extends ValueNotifier { isRecordingVideo: true, isRecordingPaused: false, isStreamingImages: streamCallback != null, - recordingOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Stops the video recording and returns the file where it was saved. @@ -321,7 +328,10 @@ class CameraController extends ValueNotifier { final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); + value = value.copyWith( + isRecordingVideo: false, + recordingOrientation: const Optional.absent(), + ); return file; } @@ -389,12 +399,16 @@ class CameraController extends ValueNotifier { Future lockCaptureOrientation() async { await CameraPlatform.instance .lockCaptureOrientation(_cameraId, value.deviceOrientation); - value = value.copyWith(lockedCaptureOrientation: value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: + Optional.of(value.deviceOrientation)); } /// Unlocks the capture orientation. Future unlockCaptureOrientation() async { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); } /// Sets the focus mode for taking pictures. @@ -428,3 +442,112 @@ class CameraController extends ValueNotifier { } } } + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera_avfoundation/example/lib/main.dart b/packages/camera/camera_avfoundation/example/lib/main.dart index af9aab1a8a86..4d98aed9a4c2 100644 --- a/packages/camera/camera_avfoundation/example/lib/main.dart +++ b/packages/camera/camera_avfoundation/example/lib/main.dart @@ -35,9 +35,11 @@ IconData getCameraLensIcon(CameraLensDirection direction) { return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; - default: - throw ArgumentError('Unknown lens direction'); } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; } void _logError(String code, String? message) { @@ -1089,5 +1091,4 @@ Future main() async { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_avfoundation/example/pubspec.yaml b/packages/camera/camera_avfoundation/example/pubspec.yaml index a9252cbd6d61..7c85ba807193 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: camera_avfoundation: diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 011616d2d9f4..5080c57a736f 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -145,6 +145,7 @@ class AVFoundationCamera extends CameraPlatform { // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { + // ignore: only_throw_errors throw error; } completer.completeError( @@ -526,9 +527,14 @@ class AVFoundationCamera extends CameraPlatform { return 'always'; case FlashMode.torch: return 'torch'; - default: - throw ArgumentError('Unknown FlashMode value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'off'; } /// Returns the resolution preset as a String. @@ -546,18 +552,23 @@ class AVFoundationCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'max'; } /// Converts messages received from the native platform into device events. Future _handleDeviceMethodCall(MethodCall call) async { switch (call.method) { case 'orientation_changed': + final Map arguments = _getArgumentDictionary(call); _deviceEventStreamController.add(DeviceOrientationChangedEvent( - deserializeDeviceOrientation( - call.arguments['orientation']! as String))); + deserializeDeviceOrientation(arguments['orientation']! as String))); break; default: throw MissingPluginException(); @@ -572,21 +583,23 @@ class AVFoundationCamera extends CameraPlatform { Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraInitializedEvent( cameraId, - call.arguments['previewWidth']! as double, - call.arguments['previewHeight']! as double, - deserializeExposureMode(call.arguments['exposureMode']! as String), - call.arguments['exposurePointSupported']! as bool, - deserializeFocusMode(call.arguments['focusMode']! as String), - call.arguments['focusPointSupported']! as bool, + arguments['previewWidth']! as double, + arguments['previewHeight']! as double, + deserializeExposureMode(arguments['exposureMode']! as String), + arguments['exposurePointSupported']! as bool, + deserializeFocusMode(arguments['focusMode']! as String), + arguments['focusPointSupported']! as bool, )); break; case 'resolution_changed': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, - call.arguments['captureWidth']! as double, - call.arguments['captureHeight']! as double, + arguments['captureWidth']! as double, + arguments['captureHeight']! as double, )); break; case 'camera_closing': @@ -595,23 +608,32 @@ class AVFoundationCamera extends CameraPlatform { )); break; case 'video_recorded': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(VideoRecordedEvent( cameraId, - XFile(call.arguments['path']! as String), - call.arguments['maxVideoDuration'] != null - ? Duration( - milliseconds: call.arguments['maxVideoDuration']! as int) + XFile(arguments['path']! as String), + arguments['maxVideoDuration'] != null + ? Duration(milliseconds: arguments['maxVideoDuration']! as int) : null, )); break; case 'error': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraErrorEvent( cameraId, - call.arguments['description']! as String, + arguments['description']! as String, )); break; default: throw MissingPluginException(); } } + + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } } diff --git a/packages/camera/camera_avfoundation/lib/src/utils.dart b/packages/camera/camera_avfoundation/lib/src/utils.dart index 663ec6da7a97..8d58f7fe1297 100644 --- a/packages/camera/camera_avfoundation/lib/src/utils.dart +++ b/packages/camera/camera_avfoundation/lib/src/utils.dart @@ -29,9 +29,14 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; - default: - throw ArgumentError('Unknown DeviceOrientation value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'portraitUp'; } /// Returns the device orientation for a given String. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index aa1c1106a774..b272a4c5c68d 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,11 +2,11 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.10 +version: 0.9.11 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 8c5fad1fec8e..5d0b74cf0c0c 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -32,14 +32,15 @@ void main() { // registerWith is called very early in initialization the bindings won't // have been initialized. While registerWith could intialize them, that // could slow down startup, so instead the handler should be set up lazily. - final ByteData? response = await TestDefaultBinaryMessengerBinding - .instance!.defaultBinaryMessenger - .handlePlatformMessage( - AVFoundationCamera.deviceEventChannelName, - const StandardMethodCodec().encodeMethodCall(const MethodCall( - 'orientation_changed', - {'orientation': 'portraitDown'})), - (ByteData? data) {}); + final ByteData? response = + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .handlePlatformMessage( + AVFoundationCamera.deviceEventChannelName, + const StandardMethodCodec().encodeMethodCall(const MethodCall( + 'orientation_changed', + {'orientation': 'portraitDown'})), + (ByteData? data) {}); expect(response, null); }); @@ -421,7 +422,8 @@ void main() { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); for (int i = 0; i < 3; i++) { - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( AVFoundationCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall( @@ -478,6 +480,9 @@ void main() { test('Should fetch CameraDescription instances for available cameras', () async { // Arrange + // This deliberately uses 'dynamic' since that's what actual platform + // channel results will be, so using typed mock data could mask type + // handling bugs in the code under test. final List returnData = [ { 'name': 'Test 1', @@ -504,11 +509,13 @@ void main() { ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { + final Map typedData = + (returnData[i] as Map).cast(); final CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name']! as String, + name: typedData['name']! as String, lensDirection: - parseCameraLensDirection(returnData[i]['lensFacing']! as String), - sensorOrientation: returnData[i]['sensorOrientation']! as int, + parseCameraLensDirection(typedData['lensFacing']! as String), + sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } @@ -1117,3 +1124,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 5cde03c2e0db..64fb555a99de 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,15 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.3.4 + +* Updates code for stricter lint checks. + +## 2.3.3 + +* Updates code for stricter lint checks. + ## 2.3.2 * Updates MethodChannelCamera to have startVideoRecording call the newer startVideoCapturing. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 34c3fa2cca36..f770499c755a 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -137,6 +137,7 @@ class MethodChannelCamera extends CameraPlatform { // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { + // ignore: only_throw_errors throw error; } completer.completeError( @@ -519,8 +520,6 @@ class MethodChannelCamera extends CameraPlatform { return 'always'; case FlashMode.torch: return 'torch'; - default: - throw ArgumentError('Unknown FlashMode value'); } } @@ -539,8 +538,6 @@ class MethodChannelCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } } @@ -551,9 +548,9 @@ class MethodChannelCamera extends CameraPlatform { Future handleDeviceMethodCall(MethodCall call) async { switch (call.method) { case 'orientation_changed': + final Map arguments = _getArgumentDictionary(call); deviceEventStreamController.add(DeviceOrientationChangedEvent( - deserializeDeviceOrientation( - call.arguments['orientation']! as String))); + deserializeDeviceOrientation(arguments['orientation']! as String))); break; default: throw MissingPluginException(); @@ -568,21 +565,23 @@ class MethodChannelCamera extends CameraPlatform { Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraInitializedEvent( cameraId, - call.arguments['previewWidth']! as double, - call.arguments['previewHeight']! as double, - deserializeExposureMode(call.arguments['exposureMode']! as String), - call.arguments['exposurePointSupported']! as bool, - deserializeFocusMode(call.arguments['focusMode']! as String), - call.arguments['focusPointSupported']! as bool, + arguments['previewWidth']! as double, + arguments['previewHeight']! as double, + deserializeExposureMode(arguments['exposureMode']! as String), + arguments['exposurePointSupported']! as bool, + deserializeFocusMode(arguments['focusMode']! as String), + arguments['focusPointSupported']! as bool, )); break; case 'resolution_changed': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, - call.arguments['captureWidth']! as double, - call.arguments['captureHeight']! as double, + arguments['captureWidth']! as double, + arguments['captureHeight']! as double, )); break; case 'camera_closing': @@ -591,23 +590,32 @@ class MethodChannelCamera extends CameraPlatform { )); break; case 'video_recorded': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(VideoRecordedEvent( cameraId, - XFile(call.arguments['path']! as String), - call.arguments['maxVideoDuration'] != null - ? Duration( - milliseconds: call.arguments['maxVideoDuration']! as int) + XFile(arguments['path']! as String), + arguments['maxVideoDuration'] != null + ? Duration(milliseconds: arguments['maxVideoDuration']! as int) : null, )); break; case 'error': + final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraErrorEvent( cameraId, - call.arguments['description']! as String, + arguments['description']! as String, )); break; default: throw MissingPluginException(); } } + + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart index 56a05cd2d0f1..6da44c98ddc8 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart @@ -18,8 +18,6 @@ String serializeExposureMode(ExposureMode exposureMode) { return 'locked'; case ExposureMode.auto: return 'auto'; - default: - throw ArgumentError('Unknown ExposureMode value'); } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart index 6baae0c1f63e..1f9cbef1bab9 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart @@ -18,8 +18,6 @@ String serializeFocusMode(FocusMode focusMode) { return 'locked'; case FocusMode.auto: return 'auto'; - default: - throw ArgumentError('Unknown FocusMode value'); } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart b/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart index edbf7d24098c..8dc69e09f58a 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart @@ -47,7 +47,6 @@ extension ImageFormatGroupName on ImageFormatGroup { case ImageFormatGroup.jpeg: return 'jpeg'; case ImageFormatGroup.unknown: - default: return 'unknown'; } } diff --git a/packages/camera/camera_platform_interface/lib/src/utils/utils.dart b/packages/camera/camera_platform_interface/lib/src/utils/utils.dart index d86880afd216..771a94be416e 100644 --- a/packages/camera/camera_platform_interface/lib/src/utils/utils.dart +++ b/packages/camera/camera_platform_interface/lib/src/utils/utils.dart @@ -30,8 +30,6 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; - default: - throw ArgumentError('Unknown DeviceOrientation value'); } } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index cb21a6c7e09c..ff024c0404ad 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -4,11 +4,11 @@ repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.3.2 +version: 2.3.4 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cross_file: ^0.3.1 diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index a58f7b4c5841..ed6151522f0c 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -484,11 +484,13 @@ void main() { ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { + final Map typedData = + (returnData[i] as Map).cast(); final CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name']! as String, - lensDirection: parseCameraLensDirection( - returnData[i]['lensFacing']! as String), - sensorOrientation: returnData[i]['sensorOrientation']! as int, + name: typedData['name']! as String, + lensDirection: + parseCameraLensDirection(typedData['lensFacing']! as String), + sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } diff --git a/packages/camera/camera_web/CHANGELOG.md b/packages/camera/camera_web/CHANGELOG.md index d8d0c93dde11..2a8d43b95e18 100644 --- a/packages/camera/camera_web/CHANGELOG.md +++ b/packages/camera/camera_web/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.3.1+1 + +* Updates code for stricter lint checks. + ## 0.3.1 * Updates to latest camera platform interface, and fails if user attempts to use streaming with recording (since streaming is currently unsupported on web). diff --git a/packages/camera/camera_web/example/integration_test/camera_test.dart b/packages/camera/camera_web/example/integration_test/camera_test.dart index 50451b9778af..705d7750e1a4 100644 --- a/packages/camera/camera_web/example/integration_test/camera_test.dart +++ b/packages/camera/camera_web/example/integration_test/camera_test.dart @@ -1286,11 +1286,10 @@ void main() { capturedVideoPartTwo, ]; - videoDataAvailableListener - ..call(FakeBlobEvent(capturedVideoPartOne)) - ..call(FakeBlobEvent(capturedVideoPartTwo)); + videoDataAvailableListener(FakeBlobEvent(capturedVideoPartOne)); + videoDataAvailableListener(FakeBlobEvent(capturedVideoPartTwo)); - videoRecordingStoppedListener.call(Event('stop')); + videoRecordingStoppedListener(Event('stop')); final XFile videoFile = await videoFileFuture; @@ -1378,7 +1377,7 @@ void main() { when(() => mediaRecorder.state).thenReturn('recording'); - videoDataAvailableListener.call(FakeBlobEvent(Blob([]))); + videoDataAvailableListener(FakeBlobEvent(Blob([]))); await Future.microtask(() {}); @@ -1412,7 +1411,7 @@ void main() { await camera.startVideoRecording(); - videoRecordingStoppedListener.call(Event('stop')); + videoRecordingStoppedListener(Event('stop')); await Future.microtask(() {}); @@ -1435,7 +1434,7 @@ void main() { await camera.startVideoRecording(); - videoRecordingStoppedListener.call(Event('stop')); + videoRecordingStoppedListener(Event('stop')); await Future.microtask(() {}); @@ -1464,7 +1463,7 @@ void main() { await camera.startVideoRecording(); - videoRecordingStoppedListener.call(Event('stop')); + videoRecordingStoppedListener(Event('stop')); await Future.microtask(() {}); @@ -1588,8 +1587,8 @@ void main() { return finalVideo!; }; - videoDataAvailableListener.call(FakeBlobEvent(Blob([]))); - videoRecordingStoppedListener.call(Event('stop')); + videoDataAvailableListener(FakeBlobEvent(Blob([]))); + videoRecordingStoppedListener(Event('stop')); expect( await streamQueue.next, diff --git a/packages/camera/camera_web/example/pubspec.yaml b/packages/camera/camera_web/example/pubspec.yaml index e82bbe392ceb..ee66870c051d 100644 --- a/packages/camera/camera_web/example/pubspec.yaml +++ b/packages/camera/camera_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/camera/camera_web/lib/src/camera_service.dart b/packages/camera/camera_web/lib/src/camera_service.dart index 6e20c7d74f78..451278c23fc3 100644 --- a/packages/camera/camera_web/lib/src/camera_service.dart +++ b/packages/camera/camera_web/lib/src/camera_service.dart @@ -299,9 +299,15 @@ class CameraService { case ResolutionPreset.medium: return const Size(720, 480); case ResolutionPreset.low: - default: return const Size(320, 240); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return const Size(320, 240); } /// Maps the given [deviceOrientation] to [OrientationType]. diff --git a/packages/camera/camera_web/pubspec.yaml b/packages/camera/camera_web/pubspec.yaml index 2368e62abee6..101444b98fe4 100644 --- a/packages/camera/camera_web/pubspec.yaml +++ b/packages/camera/camera_web/pubspec.yaml @@ -2,11 +2,11 @@ name: camera_web description: A Flutter plugin for getting information about and controlling the camera on Web. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.3.1 +version: 0.3.1+1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index f46bb667735f..34ee66815aa6 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.2.1+4 + +* Updates code for stricter lint checks. + ## 0.2.1+3 * Updates to latest camera platform interface but fails if user attempts to use streaming with recording (since streaming is currently unsupported on Windows). diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 80ce958a0e84..69ce1c330156 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: camera_platform_interface: ^2.1.2 diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart index 79dd305e2e14..4b0c1586f433 100644 --- a/packages/camera/camera_windows/lib/camera_windows.dart +++ b/packages/camera/camera_windows/lib/camera_windows.dart @@ -399,24 +399,25 @@ class CameraWindows extends CameraPlatform { ); break; case 'video_recorded': + final Map arguments = + (call.arguments as Map).cast(); + final int? maxDuration = arguments['maxVideoDuration'] as int?; // This is called if maxVideoDuration was given on record start. cameraEventStreamController.add( VideoRecordedEvent( cameraId, - XFile(call.arguments['path'] as String), - call.arguments['maxVideoDuration'] != null - ? Duration( - milliseconds: call.arguments['maxVideoDuration'] as int, - ) - : null, + XFile(arguments['path']! as String), + maxDuration != null ? Duration(milliseconds: maxDuration) : null, ), ); break; case 'error': + final Map arguments = + (call.arguments as Map).cast(); cameraEventStreamController.add( CameraErrorEvent( cameraId, - call.arguments['description'] as String, + arguments['description']! as String, ), ); break; diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 523ee14d186e..e028559c28ab 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -2,11 +2,11 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.2.1+3 +version: 0.2.1+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_windows/test/camera_windows_test.dart b/packages/camera/camera_windows/test/camera_windows_test.dart index 615020e9f17d..8d7b5d3d7185 100644 --- a/packages/camera/camera_windows/test/camera_windows_test.dart +++ b/packages/camera/camera_windows/test/camera_windows_test.dart @@ -335,11 +335,13 @@ void main() { ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { + final Map typedData = + (returnData[i] as Map).cast(); final CameraDescription cameraDescription = CameraDescription( - name: returnData[i]['name']! as String, - lensDirection: plugin.parseCameraLensDirection( - returnData[i]['lensFacing']! as String), - sensorOrientation: returnData[i]['sensorOrientation']! as int, + name: typedData['name']! as String, + lensDirection: plugin + .parseCameraLensDirection(typedData['lensFacing']! as String), + sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md index 38726df01fa6..709bd1925bbf 100644 --- a/packages/espresso/CHANGELOG.md +++ b/packages/espresso/CHANGELOG.md @@ -1,3 +1,12 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.2.0+6 + +* Updates espresso-accessibility to 3.5.1. +* Updates espresso-idling-resource to 3.5.1. + ## 0.2.0+5 * Updates android gradle plugin to 7.3.1. diff --git a/packages/espresso/android/build.gradle b/packages/espresso/android/build.gradle index ce461b1ebe55..1444eb9f8a85 100644 --- a/packages/espresso/android/build.gradle +++ b/packages/espresso/android/build.gradle @@ -70,14 +70,14 @@ dependencies { // Assertions api 'androidx.test.ext:junit:1.1.3' - api 'androidx.test.ext:truth:1.4.0' + api 'androidx.test.ext:truth:1.5.0' api 'com.google.truth:truth:0.42' // Espresso dependencies api 'androidx.test.espresso:espresso-core:3.1.0' api 'androidx.test.espresso:espresso-contrib:3.1.0' api 'androidx.test.espresso:espresso-intents:3.1.0' - api 'androidx.test.espresso:espresso-accessibility:3.1.0' + api 'androidx.test.espresso:espresso-accessibility:3.5.1' api 'androidx.test.espresso:espresso-web:3.1.0' api 'androidx.test.espresso.idling:idling-concurrent:3.1.0' @@ -85,7 +85,7 @@ dependencies { // or "androidTestImplementation", depending on whether you want the // dependency to appear on your APK's compile classpath or the test APK // classpath. - api 'androidx.test.espresso:espresso-idling-resource:3.1.0' + api 'androidx.test.espresso:espresso-idling-resource:3.5.1' } diff --git a/packages/espresso/example/pubspec.yaml b/packages/espresso/example/pubspec.yaml index 67f9edcd4644..0adf623b728a 100644 --- a/packages/espresso/example/pubspec.yaml +++ b/packages/espresso/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml index 16fcbfb3aa2c..be018c9f8274 100644 --- a/packages/espresso/pubspec.yaml +++ b/packages/espresso/pubspec.yaml @@ -3,11 +3,11 @@ description: Java classes for testing Flutter apps using Espresso. Allows driving Flutter widgets from a native Espresso test. repository: https://github.com/flutter/plugins/tree/main/packages/espresso issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+espresso%22 -version: 0.2.0+5 +version: 0.2.0+6 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 7983aa57561f..9fd2341501b3 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,3 +1,8 @@ +## NEXT + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + ## 0.9.2+2 * Improves API docs and examples. diff --git a/packages/file_selector/file_selector/example/lib/get_directory_page.dart b/packages/file_selector/file_selector/example/lib/get_directory_page.dart index de80aa56be56..dfe166db96c4 100644 --- a/packages/file_selector/file_selector/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector/example/lib/get_directory_page.dart @@ -22,10 +22,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/lib/open_image_page.dart b/packages/file_selector/file_selector/example/lib/open_image_page.dart index ba18e6e78594..7717f28c39fe 100644 --- a/packages/file_selector/file_selector/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_image_page.dart @@ -29,10 +29,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart index 8ae83c2a85dc..a09a6db9d7a7 100644 --- a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/lib/open_text_page.dart b/packages/file_selector/file_selector/example/lib/open_text_page.dart index f052db1eefc1..e28a67a02ddf 100644 --- a/packages/file_selector/file_selector/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_text_page.dart @@ -32,10 +32,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index 011d95874ae4..ff9d6d0d2e17 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -6,6 +6,7 @@ version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: file_selector: diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index ad187d6f446a..17e41cd656dd 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -7,7 +7,7 @@ version: 0.9.2+2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 439e1d4fd4c1..40d232ed25d0 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,3 +1,8 @@ +## NEXT + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + ## 0.5.0+2 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart index 606a64870566..6fcbcbfbafd6 100644 --- a/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart @@ -29,10 +29,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart index adc4a65f12b5..30cc5159b060 100644 --- a/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart @@ -34,10 +34,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart index e7bbf8bc937f..f21daf9a96bf 100644 --- a/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart @@ -26,10 +26,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml index 5a2eaa6f7dcd..175ec6c6e7d0 100644 --- a/packages/file_selector/file_selector_ios/example/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml @@ -5,6 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.14.4 <3.0.0" + flutter: ">=3.0.0" dependencies: # The following adds the Cupertino Icons font to your application. @@ -28,4 +29,4 @@ dev_dependencies: sdk: flutter flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/packages/file_selector/file_selector_ios/pigeons/messages.dart b/packages/file_selector/file_selector_ios/pigeons/messages.dart index d0ea73cde111..66706cc2406e 100644 --- a/packages/file_selector/file_selector_ios/pigeons/messages.dart +++ b/packages/file_selector/file_selector_ios/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index 3f8ecfac04ce..e772cb7d8632 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.5.0+2 environment: sdk: ">=2.14.4 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart index f66bd7dc7ced..e10ad17a2fb4 100644 --- a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart +++ b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart @@ -11,7 +11,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'file_selector_ios_test.mocks.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; @GenerateMocks([TestFileSelectorApi]) void main() { diff --git a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart index 38c91b46f65e..1d22ba75a10a 100644 --- a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart +++ b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart @@ -8,7 +8,7 @@ import 'dart:async' as _i3; import 'package:file_selector_ios/src/messages.g.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'test_api.dart' as _i2; +import 'test_api.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/file_selector/file_selector_ios/test/test_api.dart b/packages/file_selector/file_selector_ios/test/test_api.g.dart similarity index 100% rename from packages/file_selector/file_selector_ios/test/test_api.dart rename to packages/file_selector/file_selector_ios/test/test_api.g.dart diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index a1f57b5cc857..6f7853cc5f13 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,3 +1,12 @@ +## NEXT + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + +## 0.9.1 + +* Adds `getDirectoryPaths` implementation. + ## 0.9.0+1 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart index 0699dd121541..f6390ccef20d 100644 --- a/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart @@ -21,10 +21,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart new file mode 100644 index 000000000000..087240be765e --- /dev/null +++ b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart @@ -0,0 +1,87 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:flutter/material.dart'; + +/// Screen that allows the user to select one or more directories using `getDirectoryPaths`, +/// then displays the selected directories in a dialog. +class GetMultipleDirectoriesPage extends StatelessWidget { + /// Default Constructor + const GetMultipleDirectoriesPage({Key? key}) : super(key: key); + + Future _getDirectoryPaths(BuildContext context) async { + const String confirmButtonText = 'Choose'; + final List directoryPaths = + await FileSelectorPlatform.instance.getDirectoryPaths( + confirmButtonText: confirmButtonText, + ); + if (directoryPaths.isEmpty) { + // Operation was canceled by the user. + return; + } + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(directoryPaths.join('\n')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Select multiple directories'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use + primary: Colors.blue, + // ignore: deprecated_member_use + onPrimary: Colors.white, + ), + child: const Text( + 'Press to ask user to choose multiple directories'), + onPressed: () => _getDirectoryPaths(context), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class TextDisplay extends StatelessWidget { + /// Creates a `TextDisplay`. + const TextDisplay(this.directoriesPaths, {Key? key}) : super(key: key); + + /// The path selected in the dialog. + final String directoriesPaths; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Selected Directories'), + content: Scrollbar( + child: SingleChildScrollView( + child: Text(directoriesPaths), + ), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/file_selector/file_selector_linux/example/lib/home_page.dart b/packages/file_selector/file_selector_linux/example/lib/home_page.dart index a4b2ae1f63ea..80e16332a017 100644 --- a/packages/file_selector/file_selector_linux/example/lib/home_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/home_page.dart @@ -55,6 +55,13 @@ class HomePage extends StatelessWidget { child: const Text('Open a get directory dialog'), onPressed: () => Navigator.pushNamed(context, '/directory'), ), + const SizedBox(height: 10), + ElevatedButton( + style: style, + child: const Text('Open a get directories dialog'), + onPressed: () => + Navigator.pushNamed(context, '/multi-directories'), + ), ], ), ), diff --git a/packages/file_selector/file_selector_linux/example/lib/main.dart b/packages/file_selector/file_selector_linux/example/lib/main.dart index 3e447104ef9f..b8f047645a1d 100644 --- a/packages/file_selector/file_selector_linux/example/lib/main.dart +++ b/packages/file_selector/file_selector_linux/example/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'get_directory_page.dart'; +import 'get_multiple_directories_page.dart'; import 'home_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; @@ -36,6 +37,8 @@ class MyApp extends StatelessWidget { '/open/text': (BuildContext context) => const OpenTextPage(), '/save/text': (BuildContext context) => SaveTextPage(), '/directory': (BuildContext context) => const GetDirectoryPage(), + '/multi-directories': (BuildContext context) => + const GetMultipleDirectoriesPage() }, ); } diff --git a/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart b/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart index b6ada56ebb2b..9252d25f113c 100644 --- a/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart @@ -28,10 +28,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart index c8e352a5b8bd..787717cdea13 100644 --- a/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart index 4c88d7475049..97812f2b3505 100644 --- a/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart @@ -25,10 +25,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index 51bdb28717aa..f90d1c88ef97 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -1,15 +1,16 @@ name: file_selector_linux_example description: Local testbed for Linux file_selector implementation. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: file_selector_linux: path: ../ - file_selector_platform_interface: ^2.2.0 + file_selector_platform_interface: ^2.4.0 flutter: sdk: flutter diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart index 430b41c398db..b8e3df6a11bd 100644 --- a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart +++ b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart @@ -102,13 +102,26 @@ class FileSelectorLinux extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return _channel.invokeMethod( - _getDirectoryPathMethod, - { - _initialDirectoryKey: initialDirectory, - _confirmButtonTextKey: confirmButtonText, - }, - ); + final List? path = await _channel + .invokeListMethod(_getDirectoryPathMethod, { + _initialDirectoryKey: initialDirectory, + _confirmButtonTextKey: confirmButtonText, + }); + return path?.first; + } + + @override + Future> getDirectoryPaths({ + String? initialDirectory, + String? confirmButtonText, + }) async { + final List? pathList = await _channel + .invokeListMethod(_getDirectoryPathMethod, { + _initialDirectoryKey: initialDirectory, + _confirmButtonTextKey: confirmButtonText, + _multipleKey: true, + }); + return pathList ?? []; } } diff --git a/packages/file_selector/file_selector_linux/linux/.gitignore b/packages/file_selector/file_selector_linux/linux/.gitignore new file mode 100644 index 000000000000..83fee186aa98 --- /dev/null +++ b/packages/file_selector/file_selector_linux/linux/.gitignore @@ -0,0 +1,2 @@ +CMakeCache.txt +CMakeFiles/ \ No newline at end of file diff --git a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc index 833771955120..5a8cc2132595 100644 --- a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc +++ b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc @@ -192,10 +192,10 @@ static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, FlValue* args = fl_method_call_get_args(method_call); g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kOpenFileMethod) == 0) { + if (strcmp(method, kOpenFileMethod) == 0 || + strcmp(method, kGetDirectoryPathMethod) == 0) { response = show_dialog(self, method, args, true); - } else if (strcmp(method, kGetDirectoryPathMethod) == 0 || - strcmp(method, kGetSavePathMethod) == 0) { + } else if (strcmp(method, kGetSavePathMethod) == 0) { response = show_dialog(self, method, args, false); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); diff --git a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc index 84c55ac91900..8762b4a5f9f6 100644 --- a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc +++ b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc @@ -169,3 +169,17 @@ TEST(FileSelectorPlugin, TestGetDirectory) { EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), false); } + +TEST(FileSelectorPlugin, TestGetMultipleDirectories) { + g_autoptr(FlValue) args = fl_value_new_map(); + fl_value_set_string_take(args, "multiple", fl_value_new_bool(true)); + + g_autoptr(GtkFileChooserNative) dialog = + create_dialog_for_method(nullptr, "getDirectoryPath", args); + + ASSERT_NE(dialog, nullptr); + EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), + true); +} diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index a8aea37d72e2..af88485b0ef2 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -2,11 +2,11 @@ name: file_selector_linux description: Liunx implementation of the file_selector plugin. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.0+1 +version: 0.9.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -18,7 +18,7 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.2.0 + file_selector_platform_interface: ^2.4.0 flutter: sdk: flutter diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart index 748f922ae6ef..4eae078def0c 100644 --- a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart +++ b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart @@ -46,57 +46,54 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); test('passes initialDirectory correctly', () async { await plugin.openFile(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'multiple': false, + }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFile(confirmButtonText: 'Open File'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Open File', + 'multiple': false, + }, ); }); @@ -118,21 +115,20 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); }); @@ -156,57 +152,54 @@ void main() { await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': true, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': true, + }, ); }); test('passes initialDirectory correctly', () async { await plugin.openFiles(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'multiple': true, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'multiple': true, + }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFiles(confirmButtonText: 'Open File'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - 'multiple': true, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Open File', + 'multiple': true, + }, ); }); @@ -228,21 +221,20 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); }); @@ -267,57 +259,54 @@ void main() { await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); - expect( + expectMethodCall( log, - [ - isMethodCall('getSavePath', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], + 'getSavePath', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': null, + }, ); }); test('passes initialDirectory correctly', () async { await plugin.getSavePath(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('getSavePath', arguments: { - 'initialDirectory': '/example/directory', - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], + 'getSavePath', + arguments: { + 'initialDirectory': '/example/directory', + 'suggestedName': null, + 'confirmButtonText': null, + }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getSavePath(confirmButtonText: 'Open File'); - expect( + expectMethodCall( log, - [ - isMethodCall('getSavePath', arguments: { - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': 'Open File', - }), - ], + 'getSavePath', + arguments: { + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': 'Open File', + }, ); }); @@ -339,21 +328,20 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); }); @@ -362,28 +350,77 @@ void main() { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('getDirectoryPath', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - }), - ], + 'getDirectoryPath', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + }, ); }); test('passes confirmButtonText correctly', () async { - await plugin.getDirectoryPath(confirmButtonText: 'Open File'); + await plugin.getDirectoryPath(confirmButtonText: 'Select Folder'); - expect( + expectMethodCall( log, - [ - isMethodCall('getDirectoryPath', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - }), - ], + 'getDirectoryPath', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Select Folder', + }, ); }); }); + + group('#getDirectoryPaths', () { + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPaths(initialDirectory: '/example/directory'); + + expectMethodCall( + log, + 'getDirectoryPath', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'multiple': true, + }, + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPaths( + confirmButtonText: 'Select one or mode folders'); + + expectMethodCall( + log, + 'getDirectoryPath', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Select one or mode folders', + 'multiple': true, + }, + ); + }); + test('passes multiple flag correctly', () async { + await plugin.getDirectoryPaths(); + + expectMethodCall( + log, + 'getDirectoryPath', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': true, + }, + ); + }); + }); +} + +void expectMethodCall( + List log, + String methodName, { + Map? arguments, +}) { + expect(log, [isMethodCall(methodName, arguments: arguments)]); } diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index af17db8ae3ef..4fdab0b73b5d 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,3 +1,12 @@ +## NEXT + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + +## 0.9.0+4 + +* Converts platform channel to Pigeon. + ## 0.9.0+3 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart index a2a209dc9529..a3f6f6ab8798 100644 --- a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart @@ -21,10 +21,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart b/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart index b6ada56ebb2b..9252d25f113c 100644 --- a/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart @@ -28,10 +28,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart index c8e352a5b8bd..787717cdea13 100644 --- a/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart index 4c88d7475049..97812f2b3505 100644 --- a/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart @@ -25,10 +25,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift index bffc3452c49d..2dbd016f66ef 100644 --- a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift @@ -47,9 +47,13 @@ class exampleTests: XCTestCase { panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() - let call = FlutterMethodCall(methodName: "openFile", arguments: [:]) - plugin.handle(call) { result in - XCTAssertEqual((result as! [String]?)![0], returnPath) + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths[0], returnPath) called.fulfill() } @@ -72,16 +76,16 @@ class exampleTests: XCTestCase { panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() - let call = FlutterMethodCall( - methodName: "openFile", - arguments: [ - "initialDirectory": "/some/dir", - "suggestedName": "a name", - "confirmButtonText": "Open it!", - ] - ) - plugin.handle(call) { result in - XCTAssertEqual((result as! [String]?)![0], returnPath) + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + directoryPath: "/some/dir", + nameFieldStringValue: "a name", + prompt: "Open it!")) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths[0], returnPath) called.fulfill() } @@ -104,12 +108,12 @@ class exampleTests: XCTestCase { panelController.openURLs = returnPaths.map({ path in URL(fileURLWithPath: path) }) let called = XCTestExpectation() - let call = FlutterMethodCall( - methodName: "openFile", - arguments: ["multiple": true] - ) - plugin.handle(call) { result in - let paths = (result as! [String]?)! + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in XCTAssertEqual(paths.count, returnPaths.count) XCTAssertEqual(paths[0], returnPaths[0]) XCTAssertEqual(paths[1], returnPaths[1]) @@ -130,17 +134,17 @@ class exampleTests: XCTestCase { panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() - let call = FlutterMethodCall( - methodName: "openFile", - arguments: [ - "acceptedTypes": [ - "extensions": ["txt", "json"], - "UTIs": ["public.text", "public.image"], - ] - ] - ) - plugin.handle(call) { result in - XCTAssertEqual((result as! [String]?)![0], returnPath) + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + allowedFileTypes: AllowedTypes( + extensions: ["txt", "json"], + mimeTypes: [], + utis: ["public.text", "public.image"]))) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths[0], returnPath) called.fulfill() } @@ -158,9 +162,13 @@ class exampleTests: XCTestCase { panelController: panelController) let called = XCTestExpectation() - let call = FlutterMethodCall(methodName: "openFile", arguments: [:]) - plugin.handle(call) { result in - XCTAssertNil(result) + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths.count, 0) called.fulfill() } @@ -178,9 +186,9 @@ class exampleTests: XCTestCase { panelController.saveURL = URL(fileURLWithPath: returnPath) let called = XCTestExpectation() - let call = FlutterMethodCall(methodName: "getSavePath", arguments: [:]) - plugin.handle(call) { result in - XCTAssertEqual(result as! String?, returnPath) + let options = SavePanelOptions() + plugin.displaySavePanel(options: options) { path in + XCTAssertEqual(path, returnPath) called.fulfill() } @@ -198,15 +206,11 @@ class exampleTests: XCTestCase { panelController.saveURL = URL(fileURLWithPath: returnPath) let called = XCTestExpectation() - let call = FlutterMethodCall( - methodName: "getSavePath", - arguments: [ - "initialDirectory": "/some/dir", - "confirmButtonText": "Save it!", - ] - ) - plugin.handle(call) { result in - XCTAssertEqual(result as! String?, returnPath) + let options = SavePanelOptions( + directoryPath: "/some/dir", + prompt: "Save it!") + plugin.displaySavePanel(options: options) { path in + XCTAssertEqual(path, returnPath) called.fulfill() } @@ -225,9 +229,9 @@ class exampleTests: XCTestCase { panelController: panelController) let called = XCTestExpectation() - let call = FlutterMethodCall(methodName: "getSavePath", arguments: [:]) - plugin.handle(call) { result in - XCTAssertNil(result) + let options = SavePanelOptions() + plugin.displaySavePanel(options: options) { path in + XCTAssertNil(path) called.fulfill() } @@ -245,9 +249,13 @@ class exampleTests: XCTestCase { panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() - let call = FlutterMethodCall(methodName: "getDirectoryPath", arguments: [:]) - plugin.handle(call) { result in - XCTAssertEqual(result as! String?, returnPath) + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths[0], returnPath) called.fulfill() } @@ -270,9 +278,13 @@ class exampleTests: XCTestCase { panelController: panelController) let called = XCTestExpectation() - let call = FlutterMethodCall(methodName: "getDirectoryPath", arguments: [:]) - plugin.handle(call) { result in - XCTAssertNil(result) + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths.count, 0) called.fulfill() } diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index d3f3114bb481..a2122b2858b7 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: file_selector_macos: diff --git a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart index 74ce2835d18c..f8a087fa6877 100644 --- a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart +++ b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart @@ -3,17 +3,12 @@ // found in the LICENSE file. import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; -import 'package:flutter/foundation.dart' show visibleForTesting; -import 'package:flutter/services.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/file_selector_macos'); +import 'src/messages.g.dart'; /// An implementation of [FileSelectorPlatform] for macOS. class FileSelectorMacOS extends FileSelectorPlatform { - /// The MethodChannel that is being used by this implementation of the plugin. - @visibleForTesting - MethodChannel get channel => _channel; + final FileSelectorApi _hostApi = FileSelectorApi(); /// Registers the macOS implementation. static void registerWith() { @@ -26,16 +21,17 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final List? path = await _channel.invokeListMethod( - 'openFile', - { - 'acceptedTypes': _allowedTypeListFromTypeGroups(acceptedTypeGroups), - 'initialDirectory': initialDirectory, - 'confirmButtonText': confirmButtonText, - 'multiple': false, - }, - ); - return path == null ? null : XFile(path.first); + final List paths = + await _hostApi.displayOpenPanel(OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups), + directoryPath: initialDirectory, + prompt: confirmButtonText, + ))); + return paths.isEmpty ? null : XFile(paths.first!); } @override @@ -44,16 +40,17 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final List? pathList = await _channel.invokeListMethod( - 'openFile', - { - 'acceptedTypes': _allowedTypeListFromTypeGroups(acceptedTypeGroups), - 'initialDirectory': initialDirectory, - 'confirmButtonText': confirmButtonText, - 'multiple': true, - }, - ); - return pathList?.map((String path) => XFile(path)).toList() ?? []; + final List paths = + await _hostApi.displayOpenPanel(OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups), + directoryPath: initialDirectory, + prompt: confirmButtonText, + ))); + return paths.map((String? path) => XFile(path!)).toList(); } @override @@ -63,15 +60,12 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return _channel.invokeMethod( - 'getSavePath', - { - 'acceptedTypes': _allowedTypeListFromTypeGroups(acceptedTypeGroups), - 'initialDirectory': initialDirectory, - 'suggestedName': suggestedName, - 'confirmButtonText': confirmButtonText, - }, - ); + return _hostApi.displaySavePanel(SavePanelOptions( + allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups), + directoryPath: initialDirectory, + nameFieldStringValue: suggestedName, + prompt: confirmButtonText, + )); } @override @@ -79,30 +73,29 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return _channel.invokeMethod( - 'getDirectoryPath', - { - 'initialDirectory': initialDirectory, - 'confirmButtonText': confirmButtonText, - }, - ); + final List paths = + await _hostApi.displayOpenPanel(OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions( + directoryPath: initialDirectory, + prompt: confirmButtonText, + ))); + return paths.isEmpty ? null : paths.first; } // Converts the type group list into a flat list of all allowed types, since // macOS doesn't support filter groups. - Map>? _allowedTypeListFromTypeGroups( - List? typeGroups) { - const String extensionKey = 'extensions'; - const String mimeTypeKey = 'mimeTypes'; - const String utiKey = 'UTIs'; + AllowedTypes? _allowedTypesFromTypeGroups(List? typeGroups) { if (typeGroups == null || typeGroups.isEmpty) { return null; } - final Map> allowedTypes = >{ - extensionKey: [], - mimeTypeKey: [], - utiKey: [], - }; + final AllowedTypes allowedTypes = AllowedTypes( + extensions: [], + mimeTypes: [], + utis: [], + ); for (final XTypeGroup typeGroup in typeGroups) { // If any group allows everything, no filtering should be done. if (typeGroup.allowsAny) { @@ -119,9 +112,9 @@ class FileSelectorMacOS extends FileSelectorPlatform { '"mimeTypes" must be non-empty for macOS if anything is ' 'non-empty.'); } - allowedTypes[extensionKey]!.addAll(typeGroup.extensions ?? []); - allowedTypes[mimeTypeKey]!.addAll(typeGroup.mimeTypes ?? []); - allowedTypes[utiKey]!.addAll(typeGroup.macUTIs ?? []); + allowedTypes.extensions.addAll(typeGroup.extensions ?? []); + allowedTypes.mimeTypes.addAll(typeGroup.mimeTypes ?? []); + allowedTypes.utis.addAll(typeGroup.macUTIs ?? []); } return allowedTypes; diff --git a/packages/file_selector/file_selector_macos/lib/src/messages.g.dart b/packages/file_selector/file_selector_macos/lib/src/messages.g.dart new file mode 100644 index 000000000000..5f1daf94283e --- /dev/null +++ b/packages/file_selector/file_selector_macos/lib/src/messages.g.dart @@ -0,0 +1,227 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +/// A Pigeon representation of the macOS portion of an `XTypeGroup`. +class AllowedTypes { + AllowedTypes({ + required this.extensions, + required this.mimeTypes, + required this.utis, + }); + + List extensions; + + List mimeTypes; + + List utis; + + Object encode() { + return [ + extensions, + mimeTypes, + utis, + ]; + } + + static AllowedTypes decode(Object result) { + result as List; + return AllowedTypes( + extensions: (result[0] as List?)!.cast(), + mimeTypes: (result[1] as List?)!.cast(), + utis: (result[2] as List?)!.cast(), + ); + } +} + +/// Options for save panels. +/// +/// These correspond to NSSavePanel properties (which are, by extension +/// NSOpenPanel properties as well). +class SavePanelOptions { + SavePanelOptions({ + this.allowedFileTypes, + this.directoryPath, + this.nameFieldStringValue, + this.prompt, + }); + + AllowedTypes? allowedFileTypes; + + String? directoryPath; + + String? nameFieldStringValue; + + String? prompt; + + Object encode() { + return [ + allowedFileTypes?.encode(), + directoryPath, + nameFieldStringValue, + prompt, + ]; + } + + static SavePanelOptions decode(Object result) { + result as List; + return SavePanelOptions( + allowedFileTypes: result[0] != null + ? AllowedTypes.decode(result[0]! as List) + : null, + directoryPath: result[1] as String?, + nameFieldStringValue: result[2] as String?, + prompt: result[3] as String?, + ); + } +} + +/// Options for open panels. +/// +/// These correspond to NSOpenPanel properties. +class OpenPanelOptions { + OpenPanelOptions({ + required this.allowsMultipleSelection, + required this.canChooseDirectories, + required this.canChooseFiles, + required this.baseOptions, + }); + + bool allowsMultipleSelection; + + bool canChooseDirectories; + + bool canChooseFiles; + + SavePanelOptions baseOptions; + + Object encode() { + return [ + allowsMultipleSelection, + canChooseDirectories, + canChooseFiles, + baseOptions.encode(), + ]; + } + + static OpenPanelOptions decode(Object result) { + result as List; + return OpenPanelOptions( + allowsMultipleSelection: result[0]! as bool, + canChooseDirectories: result[1]! as bool, + canChooseFiles: result[2]! as bool, + baseOptions: SavePanelOptions.decode(result[3]! as List), + ); + } +} + +class _FileSelectorApiCodec extends StandardMessageCodec { + const _FileSelectorApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is AllowedTypes) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is OpenPanelOptions) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is SavePanelOptions) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return AllowedTypes.decode(readValue(buffer)!); + + case 129: + return OpenPanelOptions.decode(readValue(buffer)!); + + case 130: + return SavePanelOptions.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +class FileSelectorApi { + /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + FileSelectorApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _FileSelectorApiCodec(); + + /// Shows an open panel with the given [options], returning the list of + /// selected paths. + /// + /// An empty list corresponds to a cancelled selection. + Future> displayOpenPanel(OpenPanelOptions arg_options) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FileSelectorApi.displayOpenPanel', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_options]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as List?)!.cast(); + } + } + + /// Shows a save panel with the given [options], returning the selected path. + /// + /// A null return corresponds to a cancelled save. + Future displaySavePanel(SavePanelOptions arg_options) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FileSelectorApi.displaySavePanel', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_options]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as String?); + } + } +} diff --git a/packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift b/packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift index 9551671d1575..4e1c935dad73 100644 --- a/packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift +++ b/packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift @@ -40,7 +40,7 @@ protocol ViewProvider { var view: NSView? { get } } -public class FileSelectorPlugin: NSObject, FlutterPlugin { +public class FileSelectorPlugin: NSObject, FlutterPlugin, FileSelectorApi { private let viewProvider: ViewProvider private let panelController: PanelController @@ -49,13 +49,10 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin { private let saveMethod = "getSavePath" public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "plugins.flutter.io/file_selector_macos", - binaryMessenger: registrar.messenger) let instance = FileSelectorPlugin( viewProvider: DefaultViewProvider(registrar: registrar), panelController: DefaultPanelController()) - registrar.addMethodCallDelegate(instance, channel: channel) + FileSelectorApiSetup.setUp(binaryMessenger: registrar.messenger, api: instance) } init(viewProvider: ViewProvider, panelController: PanelController) { @@ -63,30 +60,20 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin { self.panelController = panelController } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = (call.arguments ?? [:]) as! [String: Any] - switch call.method { - case openMethod, - openDirectoryMethod: - let choosingDirectory = call.method == openDirectoryMethod - let panel = NSOpenPanel() - configure(panel: panel, with: arguments) - configure(openPanel: panel, with: arguments, choosingDirectory: choosingDirectory) - panelController.display(panel, for: viewProvider.view?.window) { (selection: [URL]?) in - if (choosingDirectory) { - result(selection?.first?.path) - } else { - result(selection?.map({ item in item.path })) - } - } - case saveMethod: - let panel = NSSavePanel() - configure(panel: panel, with: arguments) - panelController.display(panel, for: viewProvider.view?.window) { (selection: URL?) in - result(selection?.path) - } - default: - result(FlutterMethodNotImplemented) + func displayOpenPanel(options: OpenPanelOptions, completion: @escaping ([String?]) -> Void) { + + let panel = NSOpenPanel() + configure(openPanel: panel, with: options) + panelController.display(panel, for: viewProvider.view?.window) { (selection: [URL]?) in + completion(selection?.map({ item in item.path }) ?? []) + } + } + + func displaySavePanel(options: SavePanelOptions, completion: @escaping (String?) -> Void) { + let panel = NSSavePanel() + configure(panel: panel, with: options) + panelController.display(panel, for: viewProvider.view?.window) { (selection: URL?) in + completion(selection?.path) } } @@ -94,28 +81,25 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin { /// - Parameters: /// - panel: The panel to configure. /// - arguments: The arguments dictionary from a FlutterMethodCall to this plugin. - private func configure(panel: NSSavePanel, with arguments: [String: Any]) { - if let initialDirectory = getNonNullStringValue(for: "initialDirectory", from: arguments) { - panel.directoryURL = URL(fileURLWithPath: initialDirectory) + private func configure(panel: NSSavePanel, with options: SavePanelOptions) { + if let directoryPath = options.directoryPath { + panel.directoryURL = URL(fileURLWithPath: directoryPath) } - if let suggestedName = getNonNullStringValue(for: "suggestedName", from: arguments) { + if let suggestedName = options.nameFieldStringValue { panel.nameFieldStringValue = suggestedName } - if let confirmButtonText = getNonNullStringValue(for: "confirmButtonText", from: arguments) { - panel.prompt = confirmButtonText + if let prompt = options.prompt { + panel.prompt = prompt } - let acceptedTypes = getNonNullValue( - for: "acceptedTypes", - from: arguments - ) as! [String: Any]? - if let acceptedTypes = acceptedTypes { + if let acceptedTypes = options.allowedFileTypes { var allowedTypes: [String] = [] - let extensions = getNonNullStringArrayValue(for: "extensions", from: acceptedTypes) - let UTIs = getNonNullStringArrayValue(for: "UTIs", from: acceptedTypes) - allowedTypes.append(contentsOf: extensions) - allowedTypes.append(contentsOf: UTIs) - // TODO: Add support for mimeTypes in macOS 11+. + // The array values are non-null by convention even though Pigeon can't currently express + // that via the types; see messages.dart. + allowedTypes.append(contentsOf: acceptedTypes.extensions.map({ $0! })) + allowedTypes.append(contentsOf: acceptedTypes.utis.map({ $0! })) + // TODO: Add support for mimeTypes in macOS 11+. See + // https://github.com/flutter/flutter/issues/117843 if !allowedTypes.isEmpty { panel.allowedFileTypes = allowedTypes @@ -130,13 +114,12 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin { /// - choosingDirectory: True if the panel should allow choosing directories rather than files. private func configure( openPanel panel: NSOpenPanel, - with arguments: [String: Any], - choosingDirectory: Bool + with options: OpenPanelOptions ) { - panel.allowsMultipleSelection = - getNonNullValue(for: "multiple", from: arguments) as! Bool? ?? false - panel.canChooseDirectories = choosingDirectory; - panel.canChooseFiles = !choosingDirectory; + configure(panel: panel, with: options.baseOptions) + panel.allowsMultipleSelection = options.allowsMultipleSelection + panel.canChooseDirectories = options.canChooseDirectories; + panel.canChooseFiles = options.canChooseFiles; } } @@ -188,31 +171,3 @@ private class DefaultViewProvider: ViewProvider { } } } - -/// Returns the value for the given key from the provided dictionary, unless the value is NSNull -/// in which case it returns nil. -/// - Parameters: -/// - key: The key to get a value for. -/// - dictionary: The dictionary to get the value from. -/// - Returns: The value, or nil for NSNull. -private func getNonNullValue(for key: String, from dictionary: [String: Any]) -> Any? { - let value = dictionary[key]; - return value is NSNull ? nil : value; -} - -/// A convenience wrapper for getNonNullValue for string values. -private func getNonNullStringValue(for key: String, from dictionary: [String: Any]) -> String? { - return getNonNullValue(for: key, from: dictionary) as! String? -} - -/// A convenience wrapper for getNonNullValue for array-of-string values. -/// - Parameters: -/// - key: The key to get a value for. -/// - dictionary: The dictionary to get the value from. -/// - Returns: The value, or an empty array for nil for NSNull. -private func getNonNullStringArrayValue( - for key: String, - from dictionary: [String: Any] -) -> [String] { - return getNonNullValue(for: key, from: dictionary) as! [String]? ?? [] -} diff --git a/packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift b/packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift new file mode 100644 index 000000000000..75753d962525 --- /dev/null +++ b/packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift @@ -0,0 +1,228 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#else +#error("Unsupported platform.") +#endif + + +/// Generated class from Pigeon. + +/// A Pigeon representation of the macOS portion of an `XTypeGroup`. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct AllowedTypes { + var extensions: [String?] + var mimeTypes: [String?] + var utis: [String?] + + static func fromList(_ list: [Any?]) -> AllowedTypes? { + let extensions = list[0] as! [String?] + let mimeTypes = list[1] as! [String?] + let utis = list[2] as! [String?] + + return AllowedTypes( + extensions: extensions, + mimeTypes: mimeTypes, + utis: utis + ) + } + func toList() -> [Any?] { + return [ + extensions, + mimeTypes, + utis, + ] + } +} + +/// Options for save panels. +/// +/// These correspond to NSSavePanel properties (which are, by extension +/// NSOpenPanel properties as well). +/// +/// Generated class from Pigeon that represents data sent in messages. +struct SavePanelOptions { + var allowedFileTypes: AllowedTypes? = nil + var directoryPath: String? = nil + var nameFieldStringValue: String? = nil + var prompt: String? = nil + + static func fromList(_ list: [Any?]) -> SavePanelOptions? { + var allowedFileTypes: AllowedTypes? = nil + if let allowedFileTypesList = list[0] as? [Any?] { + allowedFileTypes = AllowedTypes.fromList(allowedFileTypesList) + } + let directoryPath = list[1] as? String + let nameFieldStringValue = list[2] as? String + let prompt = list[3] as? String + + return SavePanelOptions( + allowedFileTypes: allowedFileTypes, + directoryPath: directoryPath, + nameFieldStringValue: nameFieldStringValue, + prompt: prompt + ) + } + func toList() -> [Any?] { + return [ + allowedFileTypes?.toList(), + directoryPath, + nameFieldStringValue, + prompt, + ] + } +} + +/// Options for open panels. +/// +/// These correspond to NSOpenPanel properties. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct OpenPanelOptions { + var allowsMultipleSelection: Bool + var canChooseDirectories: Bool + var canChooseFiles: Bool + var baseOptions: SavePanelOptions + + static func fromList(_ list: [Any?]) -> OpenPanelOptions? { + let allowsMultipleSelection = list[0] as! Bool + let canChooseDirectories = list[1] as! Bool + let canChooseFiles = list[2] as! Bool + let baseOptions = SavePanelOptions.fromList(list[3] as! [Any?])! + + return OpenPanelOptions( + allowsMultipleSelection: allowsMultipleSelection, + canChooseDirectories: canChooseDirectories, + canChooseFiles: canChooseFiles, + baseOptions: baseOptions + ) + } + func toList() -> [Any?] { + return [ + allowsMultipleSelection, + canChooseDirectories, + canChooseFiles, + baseOptions.toList(), + ] + } +} + +private class FileSelectorApiCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return AllowedTypes.fromList(self.readValue() as! [Any]) + case 129: + return OpenPanelOptions.fromList(self.readValue() as! [Any]) + case 130: + return SavePanelOptions.fromList(self.readValue() as! [Any]) + default: + return super.readValue(ofType: type) + + } + } +} +private class FileSelectorApiCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? AllowedTypes { + super.writeByte(128) + super.writeValue(value.toList()) + } else if let value = value as? OpenPanelOptions { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? SavePanelOptions { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class FileSelectorApiCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return FileSelectorApiCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return FileSelectorApiCodecWriter(data: data) + } +} + +class FileSelectorApiCodec: FlutterStandardMessageCodec { + static let shared = FileSelectorApiCodec(readerWriter: FileSelectorApiCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol FileSelectorApi { + /// Shows an open panel with the given [options], returning the list of + /// selected paths. + /// + /// An empty list corresponds to a cancelled selection. + func displayOpenPanel(options: OpenPanelOptions, completion: @escaping ([String?]) -> Void) + /// Shows a save panel with the given [options], returning the selected path. + /// + /// A null return corresponds to a cancelled save. + func displaySavePanel(options: SavePanelOptions, completion: @escaping (String?) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class FileSelectorApiSetup { + /// The codec used by FileSelectorApi. + static var codec: FlutterStandardMessageCodec { FileSelectorApiCodec.shared } + /// Sets up an instance of `FileSelectorApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: FileSelectorApi?) { + /// Shows an open panel with the given [options], returning the list of + /// selected paths. + /// + /// An empty list corresponds to a cancelled selection. + let displayOpenPanelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.FileSelectorApi.displayOpenPanel", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + displayOpenPanelChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let optionsArg = args[0] as! OpenPanelOptions + api.displayOpenPanel(options: optionsArg) { result in + reply(wrapResult(result)) + } + } + } else { + displayOpenPanelChannel.setMessageHandler(nil) + } + /// Shows a save panel with the given [options], returning the selected path. + /// + /// A null return corresponds to a cancelled save. + let displaySavePanelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.FileSelectorApi.displaySavePanel", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + displaySavePanelChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let optionsArg = args[0] as! SavePanelOptions + api.displaySavePanel(options: optionsArg) { result in + reply(wrapResult(result)) + } + } + } else { + displaySavePanelChannel.setMessageHandler(nil) + } + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: FlutterError) -> [Any?] { + return [ + error.code, + error.message, + error.details + ] +} diff --git a/packages/path_provider/path_provider_ios/pigeons/copyright.txt b/packages/file_selector/file_selector_macos/pigeons/copyright.txt similarity index 100% rename from packages/path_provider/path_provider_ios/pigeons/copyright.txt rename to packages/file_selector/file_selector_macos/pigeons/copyright.txt diff --git a/packages/file_selector/file_selector_macos/pigeons/messages.dart b/packages/file_selector/file_selector_macos/pigeons/messages.dart new file mode 100644 index 000000000000..85b2996baf8a --- /dev/null +++ b/packages/file_selector/file_selector_macos/pigeons/messages.dart @@ -0,0 +1,84 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + input: 'pigeons/messages.dart', + swiftOut: 'macos/Classes/messages.g.swift', + dartOut: 'lib/src/messages.g.dart', + dartTestOut: 'test/messages_test.g.dart', + copyrightHeader: 'pigeons/copyright.txt', +)) + +/// A Pigeon representation of the macOS portion of an `XTypeGroup`. +class AllowedTypes { + const AllowedTypes({ + this.extensions = const [], + this.mimeTypes = const [], + this.utis = const [], + }); + + // TODO(stuartmorgan): Declare these as non-nullable generics once + // https://github.com/flutter/flutter/issues/97848 is fixed. In practice, + // the values will never be null, and the native implementation assumes that. + final List extensions; + final List mimeTypes; + final List utis; +} + +/// Options for save panels. +/// +/// These correspond to NSSavePanel properties (which are, by extension +/// NSOpenPanel properties as well). +class SavePanelOptions { + const SavePanelOptions({ + this.allowedFileTypes, + this.directoryPath, + this.nameFieldStringValue, + this.prompt, + }); + final AllowedTypes? allowedFileTypes; + final String? directoryPath; + final String? nameFieldStringValue; + final String? prompt; +} + +/// Options for open panels. +/// +/// These correspond to NSOpenPanel properties. +class OpenPanelOptions extends SavePanelOptions { + const OpenPanelOptions({ + this.allowsMultipleSelection = false, + this.canChooseDirectories = false, + this.canChooseFiles = true, + this.baseOptions = const SavePanelOptions(), + }); + final bool allowsMultipleSelection; + final bool canChooseDirectories; + final bool canChooseFiles; + // NSOpenPanel inherits from NSSavePanel, so shares all of its options. + // Ideally this would be done with inheritance rather than composition, but + // Pigeon doesn't currently support data class inheritance: + // https://github.com/flutter/flutter/issues/117819. + final SavePanelOptions baseOptions; +} + +@HostApi(dartHostTestHandler: 'TestFileSelectorApi') +abstract class FileSelectorApi { + /// Shows an open panel with the given [options], returning the list of + /// selected paths. + /// + /// An empty list corresponds to a cancelled selection. + // TODO(stuartmorgan): Declare this return as a non-nullable generic once + // https://github.com/flutter/flutter/issues/97848 is fixed. In practice, + // the values will never be null, and the calling code assumes that. + @async + List displayOpenPanel(OpenPanelOptions options); + + /// Shows a save panel with the given [options], returning the selected path. + /// + /// A null return corresponds to a cancelled save. + @async + String? displaySavePanel(SavePanelOptions options); +} diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index 3fc3832d7280..3654beaca4c0 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -2,11 +2,11 @@ name: file_selector_macos description: macOS implementation of the file_selector plugin. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.0+3 +version: 0.9.0+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -23,5 +23,8 @@ dependencies: sdk: flutter dev_dependencies: + build_runner: ^2.3.2 flutter_test: sdk: flutter + mockito: ^5.3.2 + pigeon: ^4.2.14 diff --git a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index 789d70a51777..181409e6f1b4 100644 --- a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart +++ b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart @@ -3,24 +3,32 @@ // found in the LICENSE file. import 'package:file_selector_macos/file_selector_macos.dart'; +import 'package:file_selector_macos/src/messages.g.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'file_selector_macos_test.mocks.dart'; +import 'messages_test.g.dart'; + +@GenerateMocks([TestFileSelectorApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final FileSelectorMacOS plugin = FileSelectorMacOS(); - - final List log = []; + late FileSelectorMacOS plugin; + late MockTestFileSelectorApi mockApi; setUp(() { - plugin.channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - return null; - }); - - log.clear(); + plugin = FileSelectorMacOS(); + mockApi = MockTestFileSelectorApi(); + TestFileSelectorApi.setup(mockApi); + + // Set default stubs for tests that don't expect a specific return value, + // so calls don't throw. Tests that `expect` return values should override + // these locally. + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + when(mockApi.displaySavePanel(any)).thenAnswer((_) async => null); }); test('registered instance', () { @@ -29,6 +37,33 @@ void main() { }); group('openFile', () { + test('works as expected with no arguments', () async { + when(mockApi.displayOpenPanel(any)) + .thenAnswer((_) async => ['foo']); + + final XFile? file = await plugin.openFile(); + + expect(file!.path, 'foo'); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.allowsMultipleSelection, false); + expect(options.canChooseFiles, true); + expect(options.canChooseDirectories, false); + expect(options.baseOptions.allowedFileTypes, null); + expect(options.baseOptions.directoryPath, null); + expect(options.baseOptions.nameFieldStringValue, null); + expect(options.baseOptions.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + + final XFile? file = await plugin.openFile(); + + expect(file, null); + }); + test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -46,53 +81,33 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); - expect( - log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypes': { - 'extensions': ['txt', 'jpg'], - 'mimeTypes': ['text/plain', 'image/jpg'], - 'UTIs': ['public.text', 'public.image'], - }, - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.allowedFileTypes!.extensions, + ['txt', 'jpg']); + expect(options.baseOptions.allowedFileTypes!.mimeTypes, + ['text/plain', 'image/jpg']); + expect(options.baseOptions.allowedFileTypes!.utis, + ['public.text', 'public.image']); }); test('passes initialDirectory correctly', () async { await plugin.openFile(initialDirectory: '/example/directory'); - expect( - log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypes': null, - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'multiple': false, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.directoryPath, '/example/directory'); }); test('passes confirmButtonText correctly', () async { await plugin.openFile(confirmButtonText: 'Open File'); - expect( - log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypes': null, - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - 'multiple': false, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.prompt, 'Open File'); }); test('throws for a type group that does not support macOS', () async { @@ -117,6 +132,34 @@ void main() { }); group('openFiles', () { + test('works as expected with no arguments', () async { + when(mockApi.displayOpenPanel(any)) + .thenAnswer((_) async => ['foo', 'bar']); + + final List files = await plugin.openFiles(); + + expect(files[0].path, 'foo'); + expect(files[1].path, 'bar'); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.allowsMultipleSelection, true); + expect(options.canChooseFiles, true); + expect(options.canChooseDirectories, false); + expect(options.baseOptions.allowedFileTypes, null); + expect(options.baseOptions.directoryPath, null); + expect(options.baseOptions.nameFieldStringValue, null); + expect(options.baseOptions.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + + final List files = await plugin.openFiles(); + + expect(files, isEmpty); + }); + test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -134,53 +177,33 @@ void main() { await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); - expect( - log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypes': >{ - 'extensions': ['txt', 'jpg'], - 'mimeTypes': ['text/plain', 'image/jpg'], - 'UTIs': ['public.text', 'public.image'], - }, - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': true, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.allowedFileTypes!.extensions, + ['txt', 'jpg']); + expect(options.baseOptions.allowedFileTypes!.mimeTypes, + ['text/plain', 'image/jpg']); + expect(options.baseOptions.allowedFileTypes!.utis, + ['public.text', 'public.image']); }); test('passes initialDirectory correctly', () async { await plugin.openFiles(initialDirectory: '/example/directory'); - expect( - log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypes': null, - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'multiple': true, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.directoryPath, '/example/directory'); }); test('passes confirmButtonText correctly', () async { await plugin.openFiles(confirmButtonText: 'Open File'); - expect( - log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypes': null, - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - 'multiple': true, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.prompt, 'Open File'); }); test('throws for a type group that does not support macOS', () async { @@ -205,6 +228,29 @@ void main() { }); group('getSavePath', () { + test('works as expected with no arguments', () async { + when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo'); + + final String? path = await plugin.getSavePath(); + + expect(path, 'foo'); + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes, null); + expect(options.directoryPath, null); + expect(options.nameFieldStringValue, null); + expect(options.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displaySavePanel(any)).thenAnswer((_) async => null); + + final String? path = await plugin.getSavePath(); + + expect(path, null); + }); + test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -223,53 +269,32 @@ void main() { await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); - expect( - log, - [ - isMethodCall('getSavePath', arguments: { - 'acceptedTypes': >{ - 'extensions': ['txt', 'jpg'], - 'mimeTypes': ['text/plain', 'image/jpg'], - 'UTIs': ['public.text', 'public.image'], - }, - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes!.extensions, ['txt', 'jpg']); + expect(options.allowedFileTypes!.mimeTypes, + ['text/plain', 'image/jpg']); + expect(options.allowedFileTypes!.utis, + ['public.text', 'public.image']); }); test('passes initialDirectory correctly', () async { await plugin.getSavePath(initialDirectory: '/example/directory'); - expect( - log, - [ - isMethodCall('getSavePath', arguments: { - 'acceptedTypes': null, - 'initialDirectory': '/example/directory', - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.directoryPath, '/example/directory'); }); test('passes confirmButtonText correctly', () async { await plugin.getSavePath(confirmButtonText: 'Open File'); - expect( - log, - [ - isMethodCall('getSavePath', arguments: { - 'acceptedTypes': null, - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': 'Open File', - }), - ], - ); + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.prompt, 'Open File'); }); test('throws for a type group that does not support macOS', () async { @@ -295,32 +320,49 @@ void main() { }); group('getDirectoryPath', () { + test('works as expected with no arguments', () async { + when(mockApi.displayOpenPanel(any)) + .thenAnswer((_) async => ['foo']); + + final String? path = await plugin.getDirectoryPath(); + + expect(path, 'foo'); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.allowsMultipleSelection, false); + expect(options.canChooseFiles, false); + expect(options.canChooseDirectories, true); + expect(options.baseOptions.allowedFileTypes, null); + expect(options.baseOptions.directoryPath, null); + expect(options.baseOptions.nameFieldStringValue, null); + expect(options.baseOptions.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + + final String? path = await plugin.getDirectoryPath(); + + expect(path, null); + }); + test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); - expect( - log, - [ - isMethodCall('getDirectoryPath', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.directoryPath, '/example/directory'); }); test('passes confirmButtonText correctly', () async { await plugin.getDirectoryPath(confirmButtonText: 'Open File'); - expect( - log, - [ - isMethodCall('getDirectoryPath', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - }), - ], - ); + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.prompt, 'Open File'); }); }); @@ -343,16 +385,9 @@ void main() { ), ]); - expect( - log, - [ - isMethodCall('getSavePath', arguments: { - 'acceptedTypes': null, - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], - ); + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes, null); }); } diff --git a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.mocks.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.mocks.dart new file mode 100644 index 000000000000..ddd563b2869a --- /dev/null +++ b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.mocks.dart @@ -0,0 +1,51 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in file_selector_macos/example/macos/Flutter/ephemeral/.symlinks/plugins/file_selector_macos/test/file_selector_macos_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:file_selector_macos/src/messages.g.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; + +import 'messages_test.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestFileSelectorApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestFileSelectorApi extends _i1.Mock + implements _i2.TestFileSelectorApi { + MockTestFileSelectorApi() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future> displayOpenPanel(_i4.OpenPanelOptions? options) => + (super.noSuchMethod( + Invocation.method( + #displayOpenPanel, + [options], + ), + returnValue: _i3.Future>.value([]), + ) as _i3.Future>); + @override + _i3.Future displaySavePanel(_i4.SavePanelOptions? options) => + (super.noSuchMethod( + Invocation.method( + #displaySavePanel, + [options], + ), + returnValue: _i3.Future.value(), + ) as _i3.Future); +} diff --git a/packages/file_selector/file_selector_macos/test/messages_test.g.dart b/packages/file_selector/file_selector_macos/test/messages_test.g.dart new file mode 100644 index 000000000000..731f1fb1d51f --- /dev/null +++ b/packages/file_selector/file_selector_macos/test/messages_test.g.dart @@ -0,0 +1,107 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import +// ignore_for_file: avoid_relative_lib_imports +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:file_selector_macos/src/messages.g.dart'; + +class _TestFileSelectorApiCodec extends StandardMessageCodec { + const _TestFileSelectorApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is AllowedTypes) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is OpenPanelOptions) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is SavePanelOptions) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return AllowedTypes.decode(readValue(buffer)!); + + case 129: + return OpenPanelOptions.decode(readValue(buffer)!); + + case 130: + return SavePanelOptions.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestFileSelectorApi { + static const MessageCodec codec = _TestFileSelectorApiCodec(); + + /// Shows an open panel with the given [options], returning the list of + /// selected paths. + /// + /// An empty list corresponds to a cancelled selection. + Future> displayOpenPanel(OpenPanelOptions options); + + /// Shows a save panel with the given [options], returning the selected path. + /// + /// A null return corresponds to a cancelled save. + Future displaySavePanel(SavePanelOptions options); + + static void setup(TestFileSelectorApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FileSelectorApi.displayOpenPanel', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FileSelectorApi.displayOpenPanel was null.'); + final List args = (message as List?)!; + final OpenPanelOptions? arg_options = (args[0] as OpenPanelOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.FileSelectorApi.displayOpenPanel was null, expected non-null OpenPanelOptions.'); + final List output = await api.displayOpenPanel(arg_options!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FileSelectorApi.displaySavePanel', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FileSelectorApi.displaySavePanel was null.'); + final List args = (message as List?)!; + final SavePanelOptions? arg_options = (args[0] as SavePanelOptions?); + assert(arg_options != null, + 'Argument for dev.flutter.pigeon.FileSelectorApi.displaySavePanel was null, expected non-null SavePanelOptions.'); + final String? output = await api.displaySavePanel(arg_options!); + return [output]; + }); + } + } + } +} diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index e0b08f086977..ae415ef8600d 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.4.0 * Adds `getDirectoryPaths` method to the interface. diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index 4ab63acbf7e6..b2461ee2a6d0 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.4.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cross_file: ^0.3.0 diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 5e531bb633d2..fbb58d61f999 100644 --- a/packages/file_selector/file_selector_web/CHANGELOG.md +++ b/packages/file_selector/file_selector_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.0+2 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index e14f5c2eedea..985ce35f69a8 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: file_selector_platform_interface: ^2.2.0 diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 848a41b754af..aceeb8b13693 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.0+2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 13e895ca46f1..1f9405d2c987 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,3 +1,8 @@ +## NEXT + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + ## 0.9.1+4 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart index 0699dd121541..f6390ccef20d 100644 --- a/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart @@ -21,10 +21,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart b/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart index b6ada56ebb2b..9252d25f113c 100644 --- a/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart @@ -28,10 +28,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart index c8e352a5b8bd..787717cdea13 100644 --- a/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart index 4c88d7475049..97812f2b3505 100644 --- a/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart @@ -25,10 +25,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index bc886d32c896..d270c3067325 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: file_selector_platform_interface: ^2.2.0 diff --git a/packages/file_selector/file_selector_windows/pigeons/messages.dart b/packages/file_selector/file_selector_windows/pigeons/messages.dart index f2c9ab71bd82..c3b3aff192b8 100644 --- a/packages/file_selector/file_selector_windows/pigeons/messages.dart +++ b/packages/file_selector/file_selector_windows/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', cppOptions: CppOptions(namespace: 'file_selector_windows'), cppHeaderOut: 'windows/messages.g.h', cppSourceOut: 'windows/messages.g.cpp', diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index ee0701b3fd30..a0a0f39fbd1f 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.1+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart index f07c9b67618d..62745f7df707 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart @@ -11,7 +11,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'file_selector_windows_test.mocks.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; @GenerateMocks([TestFileSelectorApi]) void main() { diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart index 61e17fcdfeaa..f60c92e6b7ee 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart @@ -5,7 +5,7 @@ import 'package:file_selector_windows/src/messages.g.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_api.dart' as _i2; +import 'test_api.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/file_selector/file_selector_windows/test/test_api.dart b/packages/file_selector/file_selector_windows/test/test_api.g.dart similarity index 100% rename from packages/file_selector/file_selector_windows/test/test_api.dart rename to packages/file_selector/file_selector_windows/test/test_api.g.dart diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md index 81202f8159de..c169487f6a81 100644 --- a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md +++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.0.7 diff --git a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml index e732497eee95..4c97e6c44cd1 100644 --- a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/flutter_plugin_android_lifecycle/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/pubspec.yaml index 3a6a2e017b53..4711d1c3629a 100644 --- a/packages/flutter_plugin_android_lifecycle/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.7 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index af701d542029..bab8412142d9 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.2.3 + +* Fixes a minor syntax error in `README.md`. + ## 2.2.2 * Modified `README.md` to fix minor syntax issues and added Code Excerpt to `README.md`. diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md index b3e3d9bc8333..687672353a7e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/README.md +++ b/packages/google_maps_flutter/google_maps_flutter/README.md @@ -153,6 +153,7 @@ class MapSampleState extends State { final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); } +} ``` See the `example` directory for a complete sample app. diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/readme_sample.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/readme_sample.dart index a15639893515..7352945fb2d5 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/readme_sample.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/readme_sample.dart @@ -68,5 +68,5 @@ class MapSampleState extends State { final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); } - // #enddocregion MapSample } +// #enddocregion MapSample diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index 06bfbbf290e4..5813d42e617e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index a037f614f2ac..0771314b9e44 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.2.2 +version: 2.2.3 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart index 9fe6923204ab..b2747c9cb5fe 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart @@ -93,7 +93,9 @@ class FakePlatformGoogleMap { Future onMethodCall(MethodCall call) { switch (call.method) { case 'map#update': - updateOptions(call.arguments['options'] as Map); + final Map arguments = + (call.arguments as Map).cast(); + updateOptions(arguments['options']! as Map); return Future.sync(() {}); case 'markers#update': updateMarkers(call.arguments as Map?); @@ -218,17 +220,18 @@ class FakePlatformGoogleMap { return result; } + // Converts a list of points expressed as two-element lists of doubles into + // a list of `LatLng`s. All list items are assumed to be non-null. List _deserializePoints(List points) { - return points.map((dynamic list) { - return LatLng(list[0] as double, list[1] as double); + return points.map((dynamic item) { + final List list = item as List; + return LatLng(list[0]! as double, list[1]! as double); }).toList(); } List> _deserializeHoles(List holes) { return holes.map>((dynamic hole) { - return hole.map((dynamic list) { - return LatLng(list[0] as double, list[1] as double); - }).toList() as List; + return _deserializePoints(hole as List); }).toList(); } diff --git a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart index b34fccbfa422..49b64b1b4b2a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart @@ -29,8 +29,12 @@ void main() { ) async { // Inject two map widgets... await tester.pumpWidget( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors Directionality( textDirection: TextDirection.ltr, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Column( children: const [ GoogleMap( diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index ac944753e8c6..6e596c135f38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,3 +1,19 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.4.3 + +* Updates code for stricter lint checks. + +## 2.4.2 + +* Updates code for stricter lint checks. + +## 2.4.1 + +* Update `androidx.test.espresso:espresso-core` to 3.5.1. + ## 2.4.0 * Adds the ability to request a specific map renderer. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle index 2d507c6cc9fa..5b383fe3bc86 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle @@ -40,7 +40,7 @@ android { implementation 'com.google.android.gms:play-services-maps:18.1.0' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:4.7.0' testImplementation 'androidx.test:core:1.2.0' diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml index a5e8bdb8bed6..aa29fa99a97b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index 11af3fe7f8df..0461b4cf71bc 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -190,81 +190,93 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); break; case 'camera#onMove': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CameraMoveEvent( mapId, - CameraPosition.fromMap(call.arguments['position'])!, + CameraPosition.fromMap(arguments['position'])!, )); break; case 'camera#onIdle': _mapEventStreamController.add(CameraIdleEvent(mapId)); break; case 'marker#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerTapEvent( mapId, - MarkerId(call.arguments['markerId'] as String), + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragStart': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragStartEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDrag': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragEnd': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEndEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'infoWindow#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(InfoWindowTapEvent( mapId, - MarkerId(call.arguments['markerId'] as String), + MarkerId(arguments['markerId']! as String), )); break; case 'polyline#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolylineTapEvent( mapId, - PolylineId(call.arguments['polylineId'] as String), + PolylineId(arguments['polylineId']! as String), )); break; case 'polygon#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolygonTapEvent( mapId, - PolygonId(call.arguments['polygonId'] as String), + PolygonId(arguments['polygonId']! as String), )); break; case 'circle#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CircleTapEvent( mapId, - CircleId(call.arguments['circleId'] as String), + CircleId(arguments['circleId']! as String), )); break; case 'map#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapTapEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, + LatLng.fromJson(arguments['position'])!, )); break; case 'map#onLongPress': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapLongPressEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, + LatLng.fromJson(arguments['position'])!, )); break; case 'tileOverlay#getTile': + final Map arguments = _getArgumentDictionary(call); final Map? tileOverlaysForThisMap = _tileOverlays[mapId]; - final String tileOverlayId = call.arguments['tileOverlayId'] as String; + final String tileOverlayId = arguments['tileOverlayId']! as String; final TileOverlay? tileOverlay = tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; final TileProvider? tileProvider = tileOverlay?.tileProvider; @@ -272,9 +284,9 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return TileProvider.noTile.toJson(); } final Tile tile = await tileProvider.getTile( - call.arguments['x'] as int, - call.arguments['y'] as int, - call.arguments['zoom'] as int?, + arguments['x']! as int, + arguments['y']! as int, + arguments['zoom'] as int?, ); return tile.toJson(); default: @@ -282,6 +294,14 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { } } + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } + @override Future updateMapOptions( Map optionsUpdate, { @@ -519,7 +539,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { preferredRenderer = 'legacy'; break; case AndroidMapRenderer.platformDefault: - default: + case null: preferredRenderer = 'default'; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index bdf2f4165408..d67e85f15e9a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.4.0 +version: 2.4.3 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 431c2472945e..6f9edad9cb71 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -37,7 +37,8 @@ void main() { int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec().encodeMethodCall(MethodCall(method, data)); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.dev/google_maps_android_$mapId', byteData, (ByteData? data) {}); } @@ -164,3 +165,9 @@ void main() { expect(widget, isA()); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md index e5f232d3ce16..a65523f426c1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 2.1.13 + +* Updates code for stricter lint checks. * Updates code for new analysis options. * Re-enable XCUITests: testUserInterface. * Remove unnecessary `RunnerUITests` target from Podfile of the example app. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml index 9c41fa57545e..ac27996fbc25 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart index 5298377763aa..a0b46f0a96d1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart @@ -172,81 +172,93 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); break; case 'camera#onMove': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CameraMoveEvent( mapId, - CameraPosition.fromMap(call.arguments['position'])!, + CameraPosition.fromMap(arguments['position'])!, )); break; case 'camera#onIdle': _mapEventStreamController.add(CameraIdleEvent(mapId)); break; case 'marker#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerTapEvent( mapId, - MarkerId(call.arguments['markerId'] as String), + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragStart': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragStartEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDrag': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragEnd': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEndEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'infoWindow#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(InfoWindowTapEvent( mapId, - MarkerId(call.arguments['markerId'] as String), + MarkerId(arguments['markerId']! as String), )); break; case 'polyline#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolylineTapEvent( mapId, - PolylineId(call.arguments['polylineId'] as String), + PolylineId(arguments['polylineId']! as String), )); break; case 'polygon#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolygonTapEvent( mapId, - PolygonId(call.arguments['polygonId'] as String), + PolygonId(arguments['polygonId']! as String), )); break; case 'circle#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CircleTapEvent( mapId, - CircleId(call.arguments['circleId'] as String), + CircleId(arguments['circleId']! as String), )); break; case 'map#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapTapEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, + LatLng.fromJson(arguments['position'])!, )); break; case 'map#onLongPress': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapLongPressEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, + LatLng.fromJson(arguments['position'])!, )); break; case 'tileOverlay#getTile': + final Map arguments = _getArgumentDictionary(call); final Map? tileOverlaysForThisMap = _tileOverlays[mapId]; - final String tileOverlayId = call.arguments['tileOverlayId'] as String; + final String tileOverlayId = arguments['tileOverlayId']! as String; final TileOverlay? tileOverlay = tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; final TileProvider? tileProvider = tileOverlay?.tileProvider; @@ -254,9 +266,9 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { return TileProvider.noTile.toJson(); } final Tile tile = await tileProvider.getTile( - call.arguments['x'] as int, - call.arguments['y'] as int, - call.arguments['zoom'] as int?, + arguments['x']! as int, + arguments['y']! as int, + arguments['zoom'] as int?, ); return tile.toJson(); default: @@ -264,6 +276,14 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { } } + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } + @override Future updateMapOptions( Map optionsUpdate, { diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml index 7ca13a9273f2..c4f8d23cb382 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter_ios description: iOS implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.1.12 +version: 2.1.13 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart index 136481cf3abb..fb23ab24aaeb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart @@ -36,7 +36,8 @@ void main() { int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec().encodeMethodCall(MethodCall(method, data)); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.dev/google_maps_ios_$mapId', byteData, (ByteData? data) {}); } @@ -122,3 +123,9 @@ void main() { equals('drag-end-marker')); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index a41d1fe487f3..b3d6c5540e7a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.2.5 + +* Updates code for stricter lint checks. + ## 2.2.4 * Updates code for `no_leading_underscores_for_local_identifiers` lint. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart index e17510f90624..3fd860e126eb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart @@ -175,81 +175,93 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); break; case 'camera#onMove': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CameraMoveEvent( mapId, - CameraPosition.fromMap(call.arguments['position'])!, + CameraPosition.fromMap(arguments['position'])!, )); break; case 'camera#onIdle': _mapEventStreamController.add(CameraIdleEvent(mapId)); break; case 'marker#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerTapEvent( mapId, - MarkerId(call.arguments['markerId'] as String), + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragStart': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragStartEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDrag': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragEnd': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEndEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, - MarkerId(call.arguments['markerId'] as String), + LatLng.fromJson(arguments['position'])!, + MarkerId(arguments['markerId']! as String), )); break; case 'infoWindow#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(InfoWindowTapEvent( mapId, - MarkerId(call.arguments['markerId'] as String), + MarkerId(arguments['markerId']! as String), )); break; case 'polyline#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolylineTapEvent( mapId, - PolylineId(call.arguments['polylineId'] as String), + PolylineId(arguments['polylineId']! as String), )); break; case 'polygon#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolygonTapEvent( mapId, - PolygonId(call.arguments['polygonId'] as String), + PolygonId(arguments['polygonId']! as String), )); break; case 'circle#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CircleTapEvent( mapId, - CircleId(call.arguments['circleId'] as String), + CircleId(arguments['circleId']! as String), )); break; case 'map#onTap': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapTapEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, + LatLng.fromJson(arguments['position'])!, )); break; case 'map#onLongPress': + final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapLongPressEvent( mapId, - LatLng.fromJson(call.arguments['position'])!, + LatLng.fromJson(arguments['position'])!, )); break; case 'tileOverlay#getTile': + final Map arguments = _getArgumentDictionary(call); final Map? tileOverlaysForThisMap = _tileOverlays[mapId]; - final String tileOverlayId = call.arguments['tileOverlayId'] as String; + final String tileOverlayId = arguments['tileOverlayId']! as String; final TileOverlay? tileOverlay = tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; final TileProvider? tileProvider = tileOverlay?.tileProvider; @@ -257,9 +269,9 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return TileProvider.noTile.toJson(); } final Tile tile = await tileProvider.getTile( - call.arguments['x'] as int, - call.arguments['y'] as int, - call.arguments['zoom'] as int?, + arguments['x']! as int, + arguments['y']! as int, + arguments['zoom'] as int?, ); return tile.toJson(); default: @@ -267,6 +279,14 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { } } + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } + @override Future updateMapOptions( Map optionsUpdate, { diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index 5639ee8c6ad7..6dfff89f8c4b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,11 +4,11 @@ repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_fl issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.2.4 +version: 2.2.5 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: collection: ^1.15.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart index e5052184915f..18743dd1f00e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart @@ -36,7 +36,8 @@ void main() { int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec() .encodeMethodCall(MethodCall(method, data)); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.io/google_maps_$mapId', byteData, (ByteData? data) {}); } @@ -120,3 +121,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 2333f7d16028..42930348965f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,5 +1,14 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 0.4.0+5 + +* Updates code for stricter lint checks. + +## 0.4.0+4 + +* Updates code for stricter lint checks. * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 0.4.0+3 diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 35c9c903e982..43f67946464a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none # Tests require flutter beta or greater to run. environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 2b09950cc00d..25cba849475b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -98,9 +98,15 @@ gmaps.MapTypeId _gmapTypeIDForPluginType(MapType type) { return gmaps.MapTypeId.HYBRID; case MapType.normal: case MapType.none: - default: return gmaps.MapTypeId.ROADMAP; } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return gmaps.MapTypeId.ROADMAP; } gmaps.MapOptions _applyInitialPosition( @@ -416,31 +422,46 @@ gmaps.PolylineOptions _polylineOptionsFromPolyline( // Translates a [CameraUpdate] into operations on a [gmaps.GMap]. void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { + // Casts [value] to a JSON dictionary (string -> nullable object). [value] + // must be a non-null JSON dictionary. + Map asJsonObject(dynamic value) { + return (value as Map).cast(); + } + + // Casts [value] to a JSON list. [value] must be a non-null JSON list. + List asJsonList(dynamic value) { + return value as List; + } + final List json = update.toJson() as List; switch (json[0]) { case 'newCameraPosition': - map.heading = json[1]['bearing'] as num?; - map.zoom = json[1]['zoom'] as num?; + final Map position = asJsonObject(json[1]); + final List latLng = asJsonList(position['target']); + map.heading = position['bearing'] as num?; + map.zoom = position['zoom'] as num?; map.panTo( - gmaps.LatLng( - json[1]['target'][0] as num?, - json[1]['target'][1] as num?, - ), + gmaps.LatLng(latLng[0] as num?, latLng[1] as num?), ); - map.tilt = json[1]['tilt'] as num?; + map.tilt = position['tilt'] as num?; break; case 'newLatLng': - map.panTo(gmaps.LatLng(json[1][0] as num?, json[1][1] as num?)); + final List latLng = asJsonList(json[1]); + map.panTo(gmaps.LatLng(latLng[0] as num?, latLng[1] as num?)); break; case 'newLatLngZoom': + final List latLng = asJsonList(json[1]); map.zoom = json[2] as num?; - map.panTo(gmaps.LatLng(json[1][0] as num?, json[1][1] as num?)); + map.panTo(gmaps.LatLng(latLng[0] as num?, latLng[1] as num?)); break; case 'newLatLngBounds': + final List latLngPair = asJsonList(json[1]); + final List latLng1 = asJsonList(latLngPair[0]); + final List latLng2 = asJsonList(latLngPair[1]); map.fitBounds( gmaps.LatLngBounds( - gmaps.LatLng(json[1][0][0] as num?, json[1][0][1] as num?), - gmaps.LatLng(json[1][1][0] as num?, json[1][1][1] as num?), + gmaps.LatLng(latLng1[0] as num?, latLng1[1] as num?), + gmaps.LatLng(latLng2[0] as num?, latLng2[1] as num?), ), ); // padding = json[2]; @@ -456,10 +477,11 @@ void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { final int newZoomDelta = zoomDelta < 0 ? zoomDelta.floor() : zoomDelta.ceil(); if (json.length == 3) { + final List latLng = asJsonList(json[2]); // With focus try { focusLatLng = - _pixelToLatLng(map, json[2][0] as int, json[2][1] as int); + _pixelToLatLng(map, latLng[0]! as int, latLng[1]! as int); } catch (e) { // https://github.com/a14n/dart-google-maps/issues/87 // print('Error computing new focus LatLng. JS Error: ' + e.toString()); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 572d9110be8e..072d584b133f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,11 +2,11 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.4.0+3 +version: 0.4.0+5 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 93497841fbd5..8888253313ba 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,12 @@ +## 5.4.4 + +* Adds documentation for iOS auth with SERVER_CLIENT_ID +* Updates minimum Flutter version to 3.0. + +## 5.4.3 + +* Updates code for stricter lint checks. + ## 5.4.2 * Updates minimum Flutter version to 2.10. diff --git a/packages/google_sign_in/google_sign_in/README.md b/packages/google_sign_in/google_sign_in/README.md index e467ca8541b9..6961bc67b7df 100644 --- a/packages/google_sign_in/google_sign_in/README.md +++ b/packages/google_sign_in/google_sign_in/README.md @@ -43,7 +43,13 @@ This plugin requires iOS 9.0 or higher. 5. Select `GoogleService-Info.plist` from the file manager. 6. A dialog will show up and ask you to select the targets, select the `Runner` target. -7. Then add the `CFBundleURLTypes` attributes below into the +7. If you need to authenticate to a backend server you can add a + `SERVER_CLIENT_ID` key value pair in your `GoogleService-Info.plist`. + ```xml + SERVER_CLIENT_ID + [YOUR SERVER CLIENT ID] + ``` +8. Then add the `CFBundleURLTypes` attributes below into the `[my_project]/ios/Runner/Info.plist` file. ```xml @@ -65,9 +71,9 @@ This plugin requires iOS 9.0 or higher. ``` -As an alternative to adding `GoogleService-Info.plist` to your Xcode project, you can instead -configure your app in Dart code. In this case, skip steps 3-6 and pass `clientId` and -`serverClientId` to the `GoogleSignIn` constructor: +As an alternative to adding `GoogleService-Info.plist` to your Xcode project, +you can instead configure your app in Dart code. In this case, skip steps 3 to 7 + and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: ```dart GoogleSignIn _googleSignIn = GoogleSignIn( @@ -79,7 +85,7 @@ GoogleSignIn _googleSignIn = GoogleSignIn( ); ``` -Note that step 7 is still required. +Note that step 8 is still required. #### iOS additional requirement diff --git a/packages/google_sign_in/google_sign_in/example/lib/main.dart b/packages/google_sign_in/google_sign_in/example/lib/main.dart index 889993b448f1..271069e6e96b 100644 --- a/packages/google_sign_in/google_sign_in/example/lib/main.dart +++ b/packages/google_sign_in/google_sign_in/example/lib/main.dart @@ -86,12 +86,14 @@ class SignInDemoState extends State { String? _pickFirstNamedContact(Map data) { final List? connections = data['connections'] as List?; final Map? contact = connections?.firstWhere( - (dynamic contact) => contact['names'] != null, + (dynamic contact) => (contact as Map)['names'] != null, orElse: () => null, ) as Map?; if (contact != null) { - final Map? name = contact['names'].firstWhere( - (dynamic name) => name['displayName'] != null, + final List names = contact['names'] as List; + final Map? name = names.firstWhere( + (dynamic name) => + (name as Map)['displayName'] != null, orElse: () => null, ) as Map?; if (name != null) { diff --git a/packages/google_sign_in/google_sign_in/example/pubspec.yaml b/packages/google_sign_in/google_sign_in/example/pubspec.yaml index fbf8f7cf0591..f1cd3828bd87 100644 --- a/packages/google_sign_in/google_sign_in/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart b/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart index 3ae022306fe6..8e908dc479ed 100644 --- a/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart +++ b/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart @@ -267,7 +267,8 @@ class GoogleSignIn { // Future that completes when we've finished calling `init` on the native side Future? _initialization; - Future _callMethod(Function method) async { + Future _callMethod( + Future Function() method) async { await _ensureInitialized(); final dynamic response = await method(); @@ -324,7 +325,7 @@ class GoogleSignIn { /// method call may be skipped, if there's already [_currentUser] information. /// This is used from the [signIn] and [signInSilently] methods. Future _addMethodCall( - Function method, { + Future Function() method, { bool canSkipCall = false, }) async { Future response; diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index c32dee78468b..056700284075 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,12 +3,11 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.4.2 - +version: 5.4.4 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index 9775c409de43..5d1b2d24d7d1 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 6.1.5 + +* Updates play-services-auth version to 20.4.1. + ## 6.1.4 * Rolls Guava to version 31.1. diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle index 19d43aa3dee4..9bc00197e03b 100644 --- a/packages/google_sign_in/google_sign_in_android/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle @@ -50,8 +50,8 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-auth:20.4.0' + implementation 'com.google.android.gms:play-services-auth:20.4.1' implementation 'com.google.guava:guava:31.1-android' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' } diff --git a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml index 5ac2240cbba1..72d8b82a9bf5 100644 --- a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index 350fe450f940..b6b581a75764 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -2,11 +2,11 @@ name: google_sign_in_android description: Android implementation of the google_sign_in plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.1.4 +version: 6.1.5 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart index c081418c7aeb..2565836f51aa 100644 --- a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart +++ b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart @@ -99,7 +99,7 @@ void main() { }); test('Other functions pass through arguments to the channel', () async { - final Map tests = { + final Map tests = { () { googleSignIn.init( hostedDomain: 'example.com', @@ -152,7 +152,7 @@ void main() { googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), }; - for (final Function f in tests.keys) { + for (final void Function() f in tests.keys) { f(); } diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md index 7c5ebc097568..495d268bde03 100644 --- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 5.5.1 * Fixes passing `serverClientId` via the channelled `init` call diff --git a/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml index aedc4b01aade..e2e643d1805d 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index 04998d8945b4..69884ca0fe70 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 5.5.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart index 45842987c736..0fee1af66120 100644 --- a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart +++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart @@ -114,7 +114,7 @@ void main() { }); test('Other functions pass through arguments to the channel', () async { - final Map tests = { + final Map tests = { () { googleSignIn.init( hostedDomain: 'example.com', @@ -155,7 +155,7 @@ void main() { googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), }; - for (final Function f in tests.keys) { + for (final void Function() f in tests.keys) { f(); } diff --git a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md index 01d54b23dae0..8adba8aa966f 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.3.0 diff --git a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml index 0902069364ce..936257b9d817 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.3.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart b/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart index 944ad3419b8e..0972d0be4855 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart @@ -95,7 +95,7 @@ void main() { }); test('Other functions pass through arguments to the channel', () async { - final Map tests = { + final Map tests = { () { googleSignIn.init( hostedDomain: 'example.com', @@ -132,7 +132,7 @@ void main() { googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), }; - for (final Function f in tests.keys) { + for (final void Function() f in tests.keys) { f(); } diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md index 2816e7284b30..85c46da8facc 100644 --- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -1,7 +1,12 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 0.10.2+1 + * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. +* Renames generated folder to js_interop. ## 0.10.2 diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart index b341d1d6b96d..b9daac44dba8 100644 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; -import 'package:google_sign_in_web/src/generated/gapiauth2.dart' as gapi; +import 'package:google_sign_in_web/src/js_interop/gapiauth2.dart' as gapi; import 'package:google_sign_in_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml index e5abdacf944d..848517534ed2 100644 --- a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart index c305cae2a33d..5d75c0da0c4f 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart @@ -11,7 +11,7 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:js/js.dart'; -import 'src/generated/gapiauth2.dart' as auth2; +import 'src/js_interop/gapiauth2.dart' as auth2; import 'src/load_gapi.dart' as gapi; import 'src/utils.dart' show gapiUserToPluginUserData; diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart similarity index 90% rename from packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart rename to packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart index a6d5b9d8dbbb..3be4b2d77b66 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart @@ -10,7 +10,9 @@ // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi -// ignore_for_file: public_member_api_docs, unused_element, sort_constructors_first, prefer_generic_function_type_aliases +// ignore_for_file: public_member_api_docs, +// * public_member_api_docs originally undocumented because the file was +// autogenerated. @JS() library gapi; @@ -18,7 +20,7 @@ library gapi; import 'package:js/js.dart'; // Module gapi -typedef void LoadCallback( +typedef LoadCallback = void Function( [dynamic args1, dynamic args2, dynamic args3, @@ -28,6 +30,11 @@ typedef void LoadCallback( @anonymous @JS() abstract class LoadConfig { + external factory LoadConfig( + {LoadCallback callback, + Function? onerror, + num? timeout, + Function? ontimeout}); external LoadCallback get callback; external set callback(LoadCallback v); external Function? get onerror; @@ -36,11 +43,6 @@ abstract class LoadConfig { external set timeout(num? v); external Function? get ontimeout; external set ontimeout(Function? v); - external factory LoadConfig( - {LoadCallback callback, - Function? onerror, - num? timeout, - Function? ontimeout}); } /*type CallbackOrConfig = LoadConfig | LoadCallback;*/ diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart b/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart similarity index 97% rename from packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart rename to packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart index f474e0d00f69..35a2d08e74b6 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart @@ -12,7 +12,11 @@ // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi.auth2 -// ignore_for_file: public_member_api_docs, unused_element, non_constant_identifier_names, sort_constructors_first, always_specify_types, strict_raw_type +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, +// * public_member_api_docs originally undocumented because the file was +// autogenerated. +// * non_constant_identifier_names required to be able to use the same parameter +// names as the underlying library. @JS() library gapiauth2; @@ -122,6 +126,15 @@ abstract class CurrentUser { @anonymous @JS() abstract class SigninOptions { + external factory SigninOptions( + {String app_package_name, + bool fetch_basic_profile, + String prompt, + String scope, + String /*'popup'|'redirect'*/ ux_mode, + String redirect_uri, + String login_hint}); + /// The package name of the Android app to install over the air. /// See Android app installs from your web site: /// https://developers.google.com/identity/sign-in/web/android-app-installs @@ -162,15 +175,6 @@ abstract class SigninOptions { // https://developers.google.com/identity/protocols/OpenIDConnect?hl=en#authenticationuriparameters external String? get login_hint; external set login_hint(String? v); - - external factory SigninOptions( - {String app_package_name, - bool fetch_basic_profile, - String prompt, - String scope, - String /*'popup'|'redirect'*/ ux_mode, - String redirect_uri, - String login_hint}); } /// Definitions by: John @@ -179,16 +183,16 @@ abstract class SigninOptions { @anonymous @JS() abstract class OfflineAccessOptions { + external factory OfflineAccessOptions( + {String scope, + String /*'select_account'|'consent'*/ prompt, + String app_package_name}); external String? get scope; external set scope(String? v); external String? /*'select_account'|'consent'*/ get prompt; external set prompt(String? /*'select_account'|'consent'*/ v); external String? get app_package_name; external set app_package_name(String? v); - external factory OfflineAccessOptions( - {String scope, - String /*'select_account'|'consent'*/ prompt, - String app_package_name}); } /// Interface that represents the different configuration parameters for the gapi.auth2.init method. @@ -196,6 +200,18 @@ abstract class OfflineAccessOptions { @anonymous @JS() abstract class ClientConfig { + external factory ClientConfig({ + String client_id, + String cookie_policy, + String scope, + bool fetch_basic_profile, + String? hosted_domain, + String openid_realm, + String /*'popup'|'redirect'*/ ux_mode, + String redirect_uri, + String plugin_name, + }); + /// The app's client ID, found and created in the Google Developers Console. external String? get client_id; external set client_id(String? v); @@ -238,18 +254,6 @@ abstract class ClientConfig { /// See: https://github.com/flutter/flutter/issues/88084 external String? get plugin_name; external set plugin_name(String? v); - - external factory ClientConfig({ - String client_id, - String cookie_policy, - String scope, - bool fetch_basic_profile, - String? hosted_domain, - String openid_realm, - String /*'popup'|'redirect'*/ ux_mode, - String redirect_uri, - String plugin_name, - }); } @JS('gapi.auth2.SigninOptionsBuilder') @@ -290,20 +294,23 @@ abstract class AuthResponse { external set first_issued_at(num? v); external num? get expires_at; external set expires_at(num? v); - external factory AuthResponse( - {String? access_token, - String? id_token, - String? login_hint, - String? scope, - num? expires_in, - num? first_issued_at, - num? expires_at}); } /// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeconfig @anonymous @JS() abstract class AuthorizeConfig { + external factory AuthorizeConfig( + {String client_id, + String scope, + String response_type, + String prompt, + String cookie_policy, + String hosted_domain, + String login_hint, + String app_package_name, + String openid_realm, + bool include_granted_scopes}); external String get client_id; external set client_id(String v); external String get scope; @@ -324,23 +331,22 @@ abstract class AuthorizeConfig { external set openid_realm(String? v); external bool? get include_granted_scopes; external set include_granted_scopes(bool? v); - external factory AuthorizeConfig( - {String client_id, - String scope, - String response_type, - String prompt, - String cookie_policy, - String hosted_domain, - String login_hint, - String app_package_name, - String openid_realm, - bool include_granted_scopes}); } /// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeresponse @anonymous @JS() abstract class AuthorizeResponse { + external factory AuthorizeResponse( + {String access_token, + String id_token, + String code, + String scope, + num expires_in, + num first_issued_at, + num expires_at, + String error, + String error_subtype}); external String get access_token; external set access_token(String v); external String get id_token; @@ -359,16 +365,6 @@ abstract class AuthorizeResponse { external set error(String v); external String get error_subtype; external set error_subtype(String v); - external factory AuthorizeResponse( - {String access_token, - String id_token, - String code, - String scope, - num expires_in, - num first_issued_at, - num expires_at, - String error, - String error_subtype}); } /// A GoogleUser object represents one user account. @@ -498,6 +494,4 @@ external void render( abstract class Promise { external factory Promise( void Function(void Function(T result) resolve, Function reject) executor); - external Promise then(void Function(T result) onFulfilled, - [Function onRejected]); } diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart index f60d6cd57e56..57b91838b8f1 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart @@ -10,7 +10,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:js/js.dart'; -import 'generated/gapi.dart' as gapi; +import 'js_interop/gapi.dart' as gapi; import 'utils.dart' show injectJSLibraries; @JS() diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart index 72424d8ea15b..45acb1ffd7ed 100644 --- a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart +++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart @@ -7,7 +7,7 @@ import 'dart:html' as html; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; -import 'generated/gapiauth2.dart' as auth2; +import 'js_interop/gapiauth2.dart' as auth2; /// Injects a list of JS [libraries] as `script` tags into a [target] [html.HtmlElement]. /// diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml index 42413e091e6e..a9d39471c3ed 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android, iOS and Web. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 0.10.2 +version: 0.10.2+1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 76192566b18b..1f138ef26118 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.8.6+1 + +* Updates code for stricter lint checks. + ## 0.8.6 * Updates minimum Flutter version to 2.10. diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index 5e448ddbee68..f4f6546b1a98 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -260,7 +260,7 @@ class _MyHomePageState extends State { ); case ConnectionState.done: return _handlePreview(); - default: + case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', diff --git a/packages/image_picker/image_picker/example/pubspec.yaml b/packages/image_picker/image_picker/example/pubspec.yaml index e9511e27ab6d..3d97877498dc 100755 --- a/packages/image_picker/image_picker/example/pubspec.yaml +++ b/packages/image_picker/image_picker/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 7fed3bf4637b..7bd8ecfd9b9b 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6 +version: 0.8.6+1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index b041761181d0..58c4951a2d4f 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.8.5+5 + +* Updates code for stricter lint checks. + ## 0.8.5+4 * Fixes null cast exception when restoring a cancelled selection. diff --git a/packages/image_picker/image_picker_android/example/lib/main.dart b/packages/image_picker/image_picker_android/example/lib/main.dart index 212e064cc6e5..d7c82a3a9979 100755 --- a/packages/image_picker/image_picker_android/example/lib/main.dart +++ b/packages/image_picker/image_picker_android/example/lib/main.dart @@ -260,7 +260,7 @@ class _MyHomePageState extends State { ); case ConnectionState.done: return _handlePreview(); - default: + case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml index 02ef8a02af4c..bfcdbad511ae 100755 --- a/packages/image_picker/image_picker_android/example/pubspec.yaml +++ b/packages/image_picker/image_picker_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 4c32e345007c..7aa1a2258645 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,11 +2,11 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/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.5+4 +version: 0.8.5+5 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 8a5c089ef807..86c1bea873ae 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.10 * Updates code for `no_leading_underscores_for_local_identifiers` lint. diff --git a/packages/image_picker/image_picker_for_web/example/pubspec.yaml b/packages/image_picker/image_picker_for_web/example/pubspec.yaml index c39bd81f9de0..96ce0dfa70c7 100644 --- a/packages/image_picker/image_picker_for_web/example/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml index c2e0975dda57..03c0fb3e3056 100644 --- a/packages/image_picker/image_picker_for_web/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.10 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index f51f46cac34c..04bb4dfdf890 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,19 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.8.6+6 + +* Updates code for stricter lint checks. + +## 0.8.6+5 + +* Fixes crash when `imageQuality` is set. + +## 0.8.6+4 + +* Fixes authorization status check for iOS14+ so it includes `PHAuthorizationStatusLimited`. + ## 0.8.6+3 * Returns error on image load failure. diff --git a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9b24f28c25cc..3504e6812840 100755 --- a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -47,16 +47,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - - - *_Nullable result, FlutterError *_Nullable error){ }]; @@ -193,7 +193,7 @@ - (void)testPickImageWithoutFullMetadata API_AVAILABLE(ios(11)) { camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@(NO) + fullMetadata:@NO completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -209,7 +209,7 @@ - (void)testPickMultiImageWithoutFullMetadata API_AVAILABLE(ios(11)) { [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@(NO) + fullMetadata:@NO completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; @@ -231,7 +231,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil - fullMetadata:@(YES) + fullMetadata:@YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -397,4 +397,46 @@ - (void)testSavesImages API_AVAILABLE(ios(14)) { [self waitForExpectationsWithTimeout:30 handler:nil]; } +- (void)testPickImageRequestAuthorization API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusNotDetermined); + OCMExpect([mockPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + handler:OCMOCK_ANY]); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery + camera:FLTSourceCameraFront] + maxSize:[[FLTMaxSize alloc] init] + quality:nil + fullMetadata:@YES + completion:^(NSString *result, FlutterError *error){ + }]; + OCMVerifyAll(mockPhotoLibrary); +} + +- (void)testPickImageAuthorizationDenied API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusDenied); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"]; + + [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery + camera:FLTSourceCameraFront] + maxSize:[[FLTMaxSize alloc] init] + quality:nil + fullMetadata:@YES + completion:^(NSString *result, FlutterError *error) { + XCTAssertNil(result); + XCTAssertEqualObjects(error.code, @"photo_access_denied"); + XCTAssertEqualObjects(error.message, @"The user did not allow photo access."); + [resultExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + @end diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m index d418354f5cc4..091755ca163b 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m @@ -184,7 +184,6 @@ - (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) { * Creates a mock picker result using NSItemProvider. * * @param itemProvider an item provider that will be used as picker result - * @param identifier local identifier of the asset */ - (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvider API_AVAILABLE(ios(14)) { diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m index e081cee9cce4..dc5693b28603 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m @@ -59,41 +59,25 @@ - (void)tearDown { [self.app terminate]; } -- (void)testPickingFromGallery { - [self launchPickerAndPick]; -} - - (void)testCancel { - [self launchPickerAndCancel]; -} - -- (void)launchPickerAndCancel { // Find and tap on the pick from gallery button. - NSPredicate *predicateToFindImageFromGalleryButton = - [NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_from_gallery"]; - XCUIElement *imageFromGalleryButton = - [self.app.otherElements elementMatchingPredicate:predicateToFindImageFromGalleryButton]; + self.app.otherElements[@"image_picker_example_from_gallery"].firstMatch; if (![imageFromGalleryButton waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find image from gallery button with %@ seconds", @(kElementWaitingTime)); } - XCTAssertTrue(imageFromGalleryButton.exists); [imageFromGalleryButton tap]; // Find and tap on the `pick` button. - NSPredicate *predicateToFindPickButton = - [NSPredicate predicateWithFormat:@"label == %@", @"PICK"]; - - XCUIElement *pickButton = [self.app.buttons elementMatchingPredicate:predicateToFindPickButton]; + XCUIElement *pickButton = self.app.buttons[@"PICK"].firstMatch; if (![pickButton waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find pick button with %@ seconds", @(kElementWaitingTime)); } - XCTAssertTrue(pickButton.exists); [pickButton tap]; // There is a known bug where the permission popups interruption won't get fired until a tap @@ -101,61 +85,70 @@ - (void)launchPickerAndCancel { [self.app tap]; // Find and tap on the `Cancel` button. - NSPredicate *predicateToFindCancelButton = - [NSPredicate predicateWithFormat:@"label == %@", @"Cancel"]; - - XCUIElement *cancelButton = - [self.app.buttons elementMatchingPredicate:predicateToFindCancelButton]; + XCUIElement *cancelButton = self.app.buttons[@"Cancel"].firstMatch; if (![cancelButton waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find Cancel button with %@ seconds", @(kElementWaitingTime)); } - XCTAssertTrue(cancelButton.exists); [cancelButton tap]; // Find the "not picked image text". - XCUIElement *imageNotPickedText = [self.app.staticTexts - elementMatchingPredicate:[NSPredicate - predicateWithFormat:@"label == %@", - @"You have not yet picked an image."]]; + XCUIElement *imageNotPickedText = + self.app.staticTexts[@"You have not yet picked an image."].firstMatch; if (![imageNotPickedText waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find imageNotPickedText with %@ seconds", @(kElementWaitingTime)); } +} - XCTAssertTrue(imageNotPickedText.exists); +- (void)testPickingFromGallery { + [self launchPickerAndPickWithMaxWidth:nil maxHeight:nil quality:nil]; } -- (void)launchPickerAndPick { - // Find and tap on the pick from gallery button. - NSPredicate *predicateToFindImageFromGalleryButton = - [NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_from_gallery"]; +- (void)testPickingWithContraintsFromGallery { + [self launchPickerAndPickWithMaxWidth:@200 maxHeight:@100 quality:@50]; +} +- (void)launchPickerAndPickWithMaxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight + quality:(NSNumber *)quality { + // Find and tap on the pick from gallery button. XCUIElement *imageFromGalleryButton = - [self.app.otherElements elementMatchingPredicate:predicateToFindImageFromGalleryButton]; + self.app.otherElements[@"image_picker_example_from_gallery"].firstMatch; if (![imageFromGalleryButton waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find image from gallery button with %@ seconds", @(kElementWaitingTime)); } - - XCTAssertTrue(imageFromGalleryButton.exists); [imageFromGalleryButton tap]; - // Find and tap on the `pick` button. - NSPredicate *predicateToFindPickButton = - [NSPredicate predicateWithFormat:@"label == %@", @"PICK"]; + if (maxWidth != nil) { + XCUIElement *field = self.app.textFields[@"Enter maxWidth if desired"].firstMatch; + [field tap]; + [field typeText:maxWidth.stringValue]; + } + + if (maxHeight != nil) { + XCUIElement *field = self.app.textFields[@"Enter maxHeight if desired"].firstMatch; + [field tap]; + [field typeText:maxHeight.stringValue]; + } - XCUIElement *pickButton = [self.app.buttons elementMatchingPredicate:predicateToFindPickButton]; + if (quality != nil) { + XCUIElement *field = self.app.textFields[@"Enter quality if desired"].firstMatch; + [field tap]; + [field typeText:quality.stringValue]; + } + + // Find and tap on the `pick` button. + XCUIElement *pickButton = self.app.buttons[@"PICK"].firstMatch; if (![pickButton waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find pick button with %@ seconds", @(kElementWaitingTime)); } - - XCTAssertTrue(pickButton.exists); [pickButton tap]; // There is a known bug where the permission popups interruption won't get fired until a tap @@ -167,8 +160,7 @@ - (void)launchPickerAndPick { if (@available(iOS 14, *)) { aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1]; } else { - XCUIElement *allPhotosCell = [self.app.cells - elementMatchingPredicate:[NSPredicate predicateWithFormat:@"label == %@", @"All Photos"]]; + XCUIElement *allPhotosCell = self.app.cells[@"All Photos"].firstMatch; if (![allPhotosCell waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find \"All Photos\" cell with %@ seconds", @@ -184,20 +176,14 @@ - (void)launchPickerAndPick { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find an image with %@ seconds", @(kElementWaitingTime)); } - XCTAssertTrue(aImage.exists); [aImage tap]; // Find the picked image. - NSPredicate *predicateToFindPickedImage = - [NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_picked_image"]; - - XCUIElement *pickedImage = [self.app.images elementMatchingPredicate:predicateToFindPickedImage]; + XCUIElement *pickedImage = self.app.images[@"image_picker_example_picked_image"].firstMatch; if (![pickedImage waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find pickedImage with %@ seconds", @(kElementWaitingTime)); } - - XCTAssertTrue(pickedImage.exists); } @end diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromLimitedGalleryUITests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromLimitedGalleryUITests.m index 455fd6269d4b..7cce0520215b 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromLimitedGalleryUITests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromLimitedGalleryUITests.m @@ -46,126 +46,67 @@ - (void)tearDown { [self.app terminate]; } -- (void)testSelectingFromGallery { - // Test the `Select Photos` button which is available after iOS 14. - if (@available(iOS 14, *)) { - [self launchPickerAndSelect]; - } else { - return; - } -} - -- (void)launchPickerAndSelect { +// Test the `Select Photos` button which is available after iOS 14. +- (void)testSelectingFromGallery API_AVAILABLE(ios(14)) { // Find and tap on the pick from gallery button. - NSPredicate *predicateToFindImageFromGalleryButton = - [NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_from_gallery"]; - XCUIElement *imageFromGalleryButton = - [self.app.otherElements elementMatchingPredicate:predicateToFindImageFromGalleryButton]; + self.app.otherElements[@"image_picker_example_from_gallery"].firstMatch; if (![imageFromGalleryButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find image from gallery button with %@ seconds", @(kLimitedElementWaitingTime)); } - - XCTAssertTrue(imageFromGalleryButton.exists); [imageFromGalleryButton tap]; // Find and tap on the `pick` button. - NSPredicate *predicateToFindPickButton = - [NSPredicate predicateWithFormat:@"label == %@", @"PICK"]; - - XCUIElement *pickButton = [self.app.buttons elementMatchingPredicate:predicateToFindPickButton]; + XCUIElement *pickButton = self.app.buttons[@"PICK"].firstMatch; if (![pickButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTSkip(@"Pick button isn't found so the test is skipped..."); } - - XCTAssertTrue(pickButton.exists); [pickButton tap]; // There is a known bug where the permission popups interruption won't get fired until a tap // happened in the app. We expect a permission popup so we do a tap here. [self.app tap]; - // Find an image and tap on it. (IOS 14 UI, images are showing directly) - XCUIElement *aImage; - if (@available(iOS 14, *)) { - aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1]; - } else { - XCUIElement *selectedPhotosCell = [self.app.cells - elementMatchingPredicate:[NSPredicate - predicateWithFormat:@"label == %@", @"Selected Photos"]]; - if (![selectedPhotosCell waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { - os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); - XCTFail(@"Failed due to not able to find \"Selected Photos\" cell with %@ seconds", - @(kLimitedElementWaitingTime)); - } - [selectedPhotosCell tap]; - aImage = [self.app.collectionViews elementMatchingType:XCUIElementTypeCollectionView - identifier:@"PhotosGridView"] - .cells.firstMatch; - } + // Find an image and tap on it. + XCUIElement *aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1]; os_log_error(OS_LOG_DEFAULT, "description before picking image %@", self.app.debugDescription); if (![aImage waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find an image with %@ seconds", @(kLimitedElementWaitingTime)); } - XCTAssertTrue(aImage.exists); + [aImage tap]; // Find and tap on the `Done` button. - NSPredicate *predicateToFindDoneButton = - [NSPredicate predicateWithFormat:@"label == %@", @"Done"]; - - XCUIElement *doneButton = [self.app.buttons elementMatchingPredicate:predicateToFindDoneButton]; + XCUIElement *doneButton = self.app.buttons[@"Done"].firstMatch; if (![doneButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTSkip(@"Permissions popup could not fired so the test is skipped..."); } - - XCTAssertTrue(doneButton.exists); [doneButton tap]; // Find an image and tap on it to have access to selected photos. - if (@available(iOS 14, *)) { - aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1]; - } else { - XCUIElement *selectedPhotosCell = [self.app.cells - elementMatchingPredicate:[NSPredicate - predicateWithFormat:@"label == %@", @"Selected Photos"]]; - if (![selectedPhotosCell waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { - os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); - XCTFail(@"Failed due to not able to find \"Selected Photos\" cell with %@ seconds", - @(kLimitedElementWaitingTime)); - } - [selectedPhotosCell tap]; - aImage = [self.app.collectionViews elementMatchingType:XCUIElementTypeCollectionView - identifier:@"PhotosGridView"] - .cells.firstMatch; - } + aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1]; + os_log_error(OS_LOG_DEFAULT, "description before picking image %@", self.app.debugDescription); if (![aImage waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find an image with %@ seconds", @(kLimitedElementWaitingTime)); } - XCTAssertTrue(aImage.exists); [aImage tap]; // Find the picked image. - NSPredicate *predicateToFindPickedImage = - [NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_picked_image"]; - - XCUIElement *pickedImage = [self.app.images elementMatchingPredicate:predicateToFindPickedImage]; + XCUIElement *pickedImage = self.app.images[@"image_picker_example_picked_image"].firstMatch; if (![pickedImage waitForExistenceWithTimeout:kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find pickedImage with %@ seconds", @(kLimitedElementWaitingTime)); } - - XCTAssertTrue(pickedImage.exists); } @end diff --git a/packages/image_picker/image_picker_ios/example/pubspec.yaml b/packages/image_picker/image_picker_ios/example/pubspec.yaml index 856f775cc641..bebe9bb04648 100755 --- a/packages/image_picker/image_picker_ios/example/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m index d5b823cf90a1..6adfd50402af 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m @@ -116,7 +116,7 @@ + (GIFInfo *)scaledGIFImage:(NSData *)data maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight { NSMutableDictionary *options = [NSMutableDictionary dictionary]; - options[(NSString *)kCGImageSourceShouldCache] = @(YES); + options[(NSString *)kCGImageSourceShouldCache] = @YES; options[(NSString *)kCGImageSourceTypeIdentifierHint] = (NSString *)kUTTypeGIF; CGImageSourceRef imageSource = diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m index 68230edb8b52..e910f8fc333b 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m @@ -365,11 +365,13 @@ - (void)checkPhotoAuthorizationWithImagePicker:(UIImagePickerController *)imageP } - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { - PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; + PHAccessLevel requestedAccessLevel = PHAccessLevelReadWrite; + PHAuthorizationStatus status = + [PHPhotoLibrary authorizationStatusForAccessLevel:requestedAccessLevel]; switch (status) { case PHAuthorizationStatusNotDetermined: { [PHPhotoLibrary - requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + requestAuthorizationForAccessLevel:requestedAccessLevel handler:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m index 9a4ae2fb00fd..11fedfb73846 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m @@ -13,9 +13,9 @@ @interface FLTPHPickerSaveImageToPathOperation () @property(strong, nonatomic) PHPickerResult *result; -@property(assign, nonatomic) NSNumber *maxHeight; -@property(assign, nonatomic) NSNumber *maxWidth; -@property(assign, nonatomic) NSNumber *desiredImageQuality; +@property(strong, nonatomic) NSNumber *maxHeight; +@property(strong, nonatomic) NSNumber *maxWidth; +@property(strong, nonatomic) NSNumber *desiredImageQuality; @property(assign, nonatomic) BOOL requestFullMetadata; @end diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart index fbc356f212b8..3f76784ff07c 100644 --- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart +++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart @@ -14,9 +14,14 @@ SourceType _convertSource(ImageSource source) { return SourceType.camera; case ImageSource.gallery: return SourceType.gallery; - default: - throw UnimplementedError('Unknown source: $source'); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError('Unknown source: $source'); } // Converts a [CameraDevice] to the corresponding Pigeon API enum value. @@ -26,9 +31,14 @@ SourceCamera _convertCamera(CameraDevice camera) { return SourceCamera.front; case CameraDevice.rear: return SourceCamera.rear; - default: - throw UnimplementedError('Unknown camera: $camera'); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError('Unknown camera: $camera'); } /// An implementation of [ImagePickerPlatform] for iOS. diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index dd8a0f0c0834..d04841b0fde9 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 44c00d7426e9..5bfb8852d2cc 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,11 +2,11 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/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.6+3 +version: 0.8.6+6 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart index b20025770ad1..2c9d52509f26 100644 --- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart +++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart @@ -8,7 +8,7 @@ import 'package:image_picker_ios/image_picker_ios.dart'; import 'package:image_picker_ios/src/messages.g.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; @immutable class _LoggedMethodCall { diff --git a/packages/image_picker/image_picker_ios/test/test_api.dart b/packages/image_picker/image_picker_ios/test/test_api.g.dart similarity index 100% rename from packages/image_picker/image_picker_ios/test/test_api.dart rename to packages/image_picker/image_picker_ios/test/test_api.g.dart diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 05b03a37cb98..91d6d80e6c23 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.6.2 * Updates imports for `prefer_relative_imports`. diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index eb4d2b649eac..2f34ee2b349c 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.6.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cross_file: ^0.3.1+1 diff --git a/packages/image_picker/image_picker_windows/CHANGELOG.md b/packages/image_picker/image_picker_windows/CHANGELOG.md index 427598760a4b..e739db71363e 100644 --- a/packages/image_picker/image_picker_windows/CHANGELOG.md +++ b/packages/image_picker/image_picker_windows/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.0+4 + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + ## 0.1.0+3 * Changes XTypeGroup initialization from final to const. diff --git a/packages/image_picker/image_picker_windows/example/lib/main.dart b/packages/image_picker/image_picker_windows/example/lib/main.dart index e340a185bf3d..dae45a5e2957 100644 --- a/packages/image_picker/image_picker_windows/example/lib/main.dart +++ b/packages/image_picker/image_picker_windows/example/lib/main.dart @@ -70,8 +70,8 @@ class _MyHomePageState extends State { } } - Future _handleMultiImagePicked(BuildContext? context) async { - await _displayPickImageDialog(context!, + Future _handleMultiImagePicked(BuildContext context) async { + await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List? pickedFileList = await _picker.pickMultiImage( @@ -91,8 +91,8 @@ class _MyHomePageState extends State { } Future _handleSingleImagePicked( - BuildContext? context, ImageSource source) async { - await _displayPickImageDialog(context!, + BuildContext context, ImageSource source) async { + await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final PickedFile? pickedFile = await _picker.pickImage( @@ -113,18 +113,20 @@ class _MyHomePageState extends State { } Future _onImageButtonPressed(ImageSource source, - {BuildContext? context, bool isMultiImage = false}) async { + {required BuildContext context, bool isMultiImage = false}) async { if (_controller != null) { await _controller!.setVolume(0.0); } - if (_isVideo) { - final PickedFile? file = await _picker.pickVideo( - source: source, maxDuration: const Duration(seconds: 10)); - await _playVideo(file); - } else if (isMultiImage) { - await _handleMultiImagePicked(context); - } else { - await _handleSingleImagePicked(context, source); + if (context.mounted) { + if (_isVideo) { + final PickedFile? file = await _picker.pickVideo( + source: source, maxDuration: const Duration(seconds: 10)); + await _playVideo(file); + } else if (isMultiImage) { + await _handleMultiImagePicked(context); + } else { + await _handleSingleImagePicked(context, source); + } } } @@ -269,7 +271,7 @@ class _MyHomePageState extends State { backgroundColor: Colors.red, onPressed: () { _isVideo = true; - _onImageButtonPressed(ImageSource.gallery); + _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', @@ -282,7 +284,7 @@ class _MyHomePageState extends State { backgroundColor: Colors.red, onPressed: () { _isVideo = true; - _onImageButtonPressed(ImageSource.camera); + _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'video1', tooltip: 'Take a Video', diff --git a/packages/image_picker/image_picker_windows/example/pubspec.yaml b/packages/image_picker/image_picker_windows/example/pubspec.yaml index b87000a6caff..bdbd182d3fc5 100644 --- a/packages/image_picker/image_picker_windows/example/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/example/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_windows/pubspec.yaml b/packages/image_picker/image_picker_windows/pubspec.yaml index 5d6988cc2931..07fa673649de 100644 --- a/packages/image_picker/image_picker_windows/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/pubspec.yaml @@ -2,11 +2,11 @@ name: image_picker_windows description: Windows platform implementation of image_picker repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.1.0+3 +version: 0.1.0+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart b/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart index 3b27c08c5fe2..f8adde4051c7 100644 --- a/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart +++ b/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart @@ -15,6 +15,12 @@ import 'image_picker_windows_test.mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); + // Returns the captured type groups from a mock call result, assuming that + // exactly one call was made and only the type groups were captured. + List capturedTypeGroups(VerificationResult result) { + return result.captured.single as List; + } + group('$ImagePickerWindows()', () { final ImagePickerWindows plugin = ImagePickerWindows(); late MockFileSelectorPlatform mockFileSelectorPlatform; @@ -42,12 +48,10 @@ void main() { test('pickImage passes the accepted type groups correctly', () async { await plugin.pickImage(source: ImageSource.gallery); - expect( - verify(mockFileSelectorPlatform.openFile( - acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))) - .captured - .single[0] - .extensions, + final VerificationResult result = verify( + mockFileSelectorPlatform.openFile( + acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); + expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.imageFormats); }); @@ -60,12 +64,10 @@ void main() { test('getImage passes the accepted type groups correctly', () async { await plugin.getImage(source: ImageSource.gallery); - expect( - verify(mockFileSelectorPlatform.openFile( - acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))) - .captured - .single[0] - .extensions, + final VerificationResult result = verify( + mockFileSelectorPlatform.openFile( + acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); + expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.imageFormats); }); @@ -78,12 +80,10 @@ void main() { test('getMultiImage passes the accepted type groups correctly', () async { await plugin.getMultiImage(); - expect( - verify(mockFileSelectorPlatform.openFiles( - acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))) - .captured - .single[0] - .extensions, + final VerificationResult result = verify( + mockFileSelectorPlatform.openFiles( + acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); + expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.imageFormats); }); }); @@ -91,12 +91,10 @@ void main() { test('pickVideo passes the accepted type groups correctly', () async { await plugin.pickVideo(source: ImageSource.gallery); - expect( - verify(mockFileSelectorPlatform.openFile( - acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))) - .captured - .single[0] - .extensions, + final VerificationResult result = verify( + mockFileSelectorPlatform.openFile( + acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); + expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.videoFormats); }); @@ -109,12 +107,10 @@ void main() { test('getVideo passes the accepted type groups correctly', () async { await plugin.getVideo(source: ImageSource.gallery); - expect( - verify(mockFileSelectorPlatform.openFile( - acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))) - .captured - .single[0] - .extensions, + final VerificationResult result = verify( + mockFileSelectorPlatform.openFile( + acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); + expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.videoFormats); }); diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 449dd5939ed7..7279685afc5d 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,3 +1,16 @@ +## 3.1.3 + +* Ignores a lint in the example app for backwards compatibility. + +## 3.1.2 + +* Updates example code for `use_build_context_synchronously` lint. +* Updates minimum Flutter version to 3.0. + +## 3.1.1 + +* Adds screenshots to pubspec.yaml. + ## 3.1.0 * Adds macOS as a supported platform. diff --git a/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle index 9bb39660cf05..b4a405c9d9b8 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle +++ b/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle @@ -108,7 +108,7 @@ flutter { dependencies { implementation 'com.android.billingclient:billing:3.0.2' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' testImplementation 'org.json:json:20220924' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart index 9e53b4bf8b8e..21268d4e7e8a 100644 --- a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart @@ -164,6 +164,8 @@ class _MyAppState extends State<_MyApp> { } if (_purchasePending) { stack.add( + // TODO(goderbauer): Make this const when that's available on stable. + // ignore: prefer_const_constructors Stack( children: const [ Opacity( @@ -468,17 +470,19 @@ class _MyAppState extends State<_MyApp> { await androidAddition.launchPriceChangeConfirmationFlow( sku: 'purchaseId', ); - if (priceChangeConfirmationResult.responseCode == BillingResponse.ok) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Price change accepted'), - )); - } else { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - priceChangeConfirmationResult.debugMessage ?? - 'Price change failed with code ${priceChangeConfirmationResult.responseCode}', - ), - )); + if (context.mounted) { + if (priceChangeConfirmationResult.responseCode == BillingResponse.ok) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Price change accepted'), + )); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + priceChangeConfirmationResult.debugMessage ?? + 'Price change failed with code ${priceChangeConfirmationResult.responseCode}', + ), + )); + } } } if (Platform.isIOS) { diff --git a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml index 6d2297572cb9..8037b1a4c1ef 100644 --- a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index a062d09d59e4..443487465a27 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -2,11 +2,11 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 3.1.0 +version: 3.1.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -34,3 +34,9 @@ dev_dependencies: sdk: flutter plugin_platform_interface: ^2.0.0 test: ^1.16.0 + +screenshots: + - description: 'Example of in-app purchase on ios' + path: doc/iap_ios.gif + - description: 'Example of in-app purchase on android' + path: doc/iap_android.gif diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 4aa14a8b6b59..76c94cbab35c 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.2.4+1 + +* Updates Google Play Billing Library to 5.1.0. +* Updates androidx.annotation to 1.5.0. + +## 0.2.4 + +* Updates minimum Flutter version to 3.0. +* Ignores a lint in the example app for backwards compatibility. + +## 0.2.3+9 + +* Updates `androidx.test.espresso:espresso-core` to 3.5.1. + +## 0.2.3+8 + +* Updates code for stricter lint checks. + ## 0.2.3+7 * Updates code for new analysis options. diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index 704ab36c253b..fe9a958580ba 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -54,11 +54,11 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.3.0' - implementation 'com.android.billingclient:billing:5.0.0' + implementation 'androidx.annotation:annotation:1.5.0' + implementation 'com.android.billingclient:billing:5.1.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20220924' testImplementation 'org.mockito:mockito-core:4.7.0' androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index cc612d1918b6..97e71b038be3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -156,6 +156,8 @@ class _MyAppState extends State<_MyApp> { } if (_purchasePending) { stack.add( + // TODO(goderbauer): Make this const when that's available on stable. + // ignore: prefer_const_constructors Stack( children: const [ Opacity( diff --git a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml index af760a3ada46..d5a76b848093 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index b64eaab49a9d..2d4a3f96b50e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -343,8 +343,12 @@ class BillingClient { (call.arguments as Map).cast())); break; case _kOnBillingServiceDisconnected: - final int handle = call.arguments['handle'] as int; - await _callbacks[_kOnBillingServiceDisconnected]![handle](); + final int handle = + (call.arguments as Map)['handle']! as int; + final List onDisconnected = + _callbacks[_kOnBillingServiceDisconnected]! + .cast(); + onDisconnected[handle](); break; } } diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index d70e5dfa0e1a..397e82a82446 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,11 +2,11 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.2.3+7 +version: 0.2.4+1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index b6055cc9a8bb..70e519ce9f6e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -426,7 +426,8 @@ void main() { name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { - final String purchaseToken = args['purchaseToken'] as String; + final String purchaseToken = + (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); @@ -540,7 +541,8 @@ void main() { name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { - final String purchaseToken = args['purchaseToken'] as String; + final String purchaseToken = + (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); @@ -617,7 +619,8 @@ void main() { name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { - final String purchaseToken = args['purchaseToken'] as String; + final String purchaseToken = + (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); @@ -682,7 +685,8 @@ void main() { name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { - final String purchaseToken = args['purchaseToken'] as String; + final String purchaseToken = + (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md index 17ba02986088..a408c2db2cd7 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.3.2 * Updates imports for `prefer_relative_imports`. diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml index 46e38b0a03fa..b3420161530b 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 1.3.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 3839419a32cf..569e0717fd38 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,10 +1,27 @@ +## 0.3.5+2 + +* Fix a crash when `appStoreReceiptURL` is nil. + +## 0.3.5+1 + +* Uses the new `sharedDarwinSource` flag when available. + +## 0.3.5 + +* Updates minimum Flutter version to 3.0. +* Ignores a lint in the example app for backwards compatibility. + +## 0.3.4+1 + +* Updates code for stricter lint checks. + ## 0.3.4 * Adds macOS as a supported platform. ## 0.3.3 -* Supports adding discount information to AppStorePurchaseParam. +* Supports adding discount information to AppStorePurchaseParam. * Fixes iOS Promotional Offers bug which prevents them from working. ## 0.3.2+2 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m similarity index 97% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m index fc125da133d4..320e6072d046 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m @@ -21,6 +21,9 @@ @implementation FIAPReceiptManager - (NSString *)retrieveReceiptWithError:(FlutterError **)flutterError { NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + if (!receiptURL) { + return nil; + } NSError *receiptError; NSData *receipt = [self getReceiptData:receiptURL error:&receiptError]; if (!receipt || receiptError) { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/in_app_purchase_storekit.podspec rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart index 09058ea2e89a..ce06aa1d1ab6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart @@ -156,6 +156,8 @@ class _MyAppState extends State<_MyApp> { } if (_purchasePending) { stack.add( + // TODO(goderbauer): Make this const when that's available on stable. + // ignore: prefer_const_constructors Stack( children: const [ Opacity( diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml index e71b85d4b447..b06dd6a9a594 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 9ace425ce1dc..f7e6dcdaab16 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -313,6 +313,23 @@ - (void)testRetrieveReceiptDataSuccess { XCTAssert([result isKindOfClass:[NSString class]]); } +- (void)testRetrieveReceiptDataNil { + NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); + OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); + XCTestExpectation *expectation = [self expectationWithDescription:@"nil receipt data retrieved"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" + arguments:nil]; + __block NSDictionary *result; + [self.plugin handleMethodCall:call + result:^(id r) { + result = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertNil(result); +} + - (void)testRetrieveReceiptDataError { XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; FlutterMethodCall *call = [FlutterMethodCall diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep b/packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep deleted file mode 120000 index bf2007784034..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -../../shared/Assets/.gitkeep \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h index 8c80f07ea9a6..6b974bc7d268 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.h \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m index 643df24599b8..f9b4ffe6732d 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.m \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h index 5e54d74d187a..e4b452397bc2 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m index f972e7d7c7e8..a1b95ef97c1b 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h index f5c64da51bf3..88f02af0b00a 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.h \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m index 7cc0593abb34..f303c3c162a0 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.m \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h index b008c38df4bb..9eb31f26b048 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.h \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m index 22a1ba3a7c48..d6976dc0dd26 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.m \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h index 8a64356be52e..6bc9c2f6dc85 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.h \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m index 87359d2e1c55..8c892d29f1e6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.m \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h index 1f8f3f92da93..8862d80dde39 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.h \ No newline at end of file +../../darwin/Classes/FIATransactionCache.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m index b27e9811319e..8c0dd87c7e97 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.m \ No newline at end of file +../../darwin/Classes/FIATransactionCache.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h index d92777687ecd..0ec6c66d54f8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.h \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m index 67f61aad1fb0..e087d55187e8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.m \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec index 79982cb307de..4157364db8d6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec @@ -1 +1 @@ -../shared/in_app_purchase_storekit.podspec \ No newline at end of file +../darwin/in_app_purchase_storekit.podspec \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index d360a2da3fe5..859946b557bf 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -245,11 +245,13 @@ class SKPaymentQueueWrapper { } case 'shouldAddStorePayment': { + final Map arguments = + call.arguments as Map; final SKPaymentWrapper payment = SKPaymentWrapper.fromJson( - (call.arguments['payment'] as Map) + (arguments['payment']! as Map) .cast()); final SKProductWrapper product = SKProductWrapper.fromJson( - (call.arguments['product'] as Map) + (arguments['product']! as Map) .cast()); return Future(() { if (observer.shouldAddStorePayment( @@ -290,12 +292,14 @@ class SKPaymentQueueWrapper { final SKPaymentQueueDelegateWrapper delegate = _paymentQueueDelegate!; switch (call.method) { case 'shouldContinueTransaction': + final Map arguments = + call.arguments as Map; final SKPaymentTransactionWrapper transaction = SKPaymentTransactionWrapper.fromJson( - (call.arguments['transaction'] as Map) + (arguments['transaction']! as Map) .cast()); final SKStorefrontWrapper storefront = SKStorefrontWrapper.fromJson( - (call.arguments['storefront'] as Map) + (arguments['storefront']! as Map) .cast()); return delegate.shouldContinueTransaction(transaction, storefront); case 'shouldShowPriceConsent': diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep b/packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep deleted file mode 120000 index bf2007784034..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -../../shared/Assets/.gitkeep \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h index 8c80f07ea9a6..6b974bc7d268 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.h \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m index 643df24599b8..f9b4ffe6732d 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.m \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h index 5e54d74d187a..e4b452397bc2 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m index f972e7d7c7e8..a1b95ef97c1b 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h index f5c64da51bf3..88f02af0b00a 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.h \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m index 7cc0593abb34..f303c3c162a0 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.m \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h index b008c38df4bb..9eb31f26b048 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.h \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m index 22a1ba3a7c48..d6976dc0dd26 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.m \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h index 8a64356be52e..6bc9c2f6dc85 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.h \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m index 87359d2e1c55..8c892d29f1e6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.m \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h index 1f8f3f92da93..8862d80dde39 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.h \ No newline at end of file +../../darwin/Classes/FIATransactionCache.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m index b27e9811319e..8c0dd87c7e97 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.m \ No newline at end of file +../../darwin/Classes/FIATransactionCache.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h index d92777687ecd..0ec6c66d54f8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.h \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m index 67f61aad1fb0..e087d55187e8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.m \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec index 79982cb307de..4157364db8d6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec @@ -1 +1 @@ -../shared/in_app_purchase_storekit.podspec \ No newline at end of file +../darwin/in_app_purchase_storekit.podspec \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index f45b3acaad47..78ae8b16d524 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,11 +2,11 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.4 +version: 0.3.5+2 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -14,8 +14,10 @@ flutter: platforms: ios: pluginClass: InAppPurchasePlugin + sharedDarwinSource: true macos: pluginClass: InAppPurchasePlugin + sharedDarwinSource: true dependencies: collection: ^1.15.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Assets/.gitkeep b/packages/in_app_purchase/in_app_purchase_storekit/shared/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index e6b9696c8cb1..dfc715c86b4d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -169,14 +169,15 @@ class FakeStoreKitPlatform { receiptData = 'refreshed receipt data'; return Future.sync(() {}); case '-[InAppPurchasePlugin addPayment:result:]': - final String id = call.arguments['productIdentifier'] as String; - final int quantity = call.arguments['quantity'] as int; + final Map arguments = _getArgumentDictionary(call); + final String id = arguments['productIdentifier']! as String; + final int quantity = arguments['quantity']! as int; // Keep the received paymentDiscount parameter when testing payment with discount. - if (call.arguments['applicationUsername'] == 'userWithDiscount') { - if (call.arguments['paymentDiscount'] != null) { - final Map discountArgument = - call.arguments['paymentDiscount'] as Map; + if (arguments['applicationUsername']! == 'userWithDiscount') { + final Map? discountArgument = + arguments['paymentDiscount'] as Map?; + if (discountArgument != null) { discountReceived = discountArgument.cast(); } else { discountReceived = {}; @@ -210,9 +211,10 @@ class FakeStoreKitPlatform { } break; case '-[InAppPurchasePlugin finishTransaction:result:]': + final Map arguments = _getArgumentDictionary(call); finishedTransactions.add(createPurchasedTransaction( - call.arguments['productIdentifier'] as String, - call.arguments['transactionIdentifier'] as String, + arguments['productIdentifier']! as String, + arguments['transactionIdentifier']! as String, quantity: transactions.first.payment.quantity)); break; case '-[SKPaymentQueue startObservingTransactionQueue]': @@ -224,4 +226,12 @@ class FakeStoreKitPlatform { } return Future.sync(() {}); } + + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index ff059ffbb1fc..4936f62a911a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -234,7 +234,7 @@ class FakeStoreKitPlatform { // receipt manager case '-[InAppPurchasePlugin retrieveReceiptData:result:]': if (getReceiptFailTest) { - throw 'some arbitrary error'; + throw Exception('some arbitrary error'); } return Future.value('receipt data'); // payment queue diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index 610c362a00db..63f10450cfd7 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+1 + +* Add lint ignore comments + ## 0.2.1 * Updates minimum Flutter version to 3.3.0. diff --git a/packages/ios_platform_images/example/pubspec.yaml b/packages/ios_platform_images/example/pubspec.yaml index 6045b3f67cfc..49b09bd8b637 100644 --- a/packages/ios_platform_images/example/pubspec.yaml +++ b/packages/ios_platform_images/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.2 diff --git a/packages/ios_platform_images/lib/ios_platform_images.dart b/packages/ios_platform_images/lib/ios_platform_images.dart index aeb875ad2463..b372d362f6f7 100644 --- a/packages/ios_platform_images/lib/ios_platform_images.dart +++ b/packages/ios_platform_images/lib/ios_platform_images.dart @@ -63,7 +63,9 @@ class _FutureMemoryImage extends ImageProvider<_FutureMemoryImage> { @override ImageStreamCompleter loadBuffer( - _FutureMemoryImage key, DecoderBufferCallback decode) { + _FutureMemoryImage key, + DecoderBufferCallback decode, // ignore: deprecated_member_use + ) { return _FutureImageStreamCompleter( codec: _loadAsync(key, decode), futureScale: _futureScale, @@ -72,7 +74,7 @@ class _FutureMemoryImage extends ImageProvider<_FutureMemoryImage> { Future _loadAsync( _FutureMemoryImage key, - DecoderBufferCallback decode, + DecoderBufferCallback decode, // ignore: deprecated_member_use ) { assert(key == this); return _futureBytes.then(ui.ImmutableBuffer.fromUint8List).then(decode); diff --git a/packages/ios_platform_images/pubspec.yaml b/packages/ios_platform_images/pubspec.yaml index 17fb8850ac1d..6a321fed4875 100644 --- a/packages/ios_platform_images/pubspec.yaml +++ b/packages/ios_platform_images/pubspec.yaml @@ -2,7 +2,7 @@ name: ios_platform_images description: A plugin to share images between Flutter and iOS in add-to-app setups. repository: https://github.com/flutter/plugins/tree/main/packages/ios_platform_images issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+ios_platform_images%22 -version: 0.2.1 +version: 0.2.1+1 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index 5d340bb7399c..d5ad7aa9a28a 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,6 +1,6 @@ -## 2.1.4 +## NEXT -* Updates documentation for Android version 8 and below theme compatibility. +* Updates minimum Flutter version to 3.0. ## 2.1.3 diff --git a/packages/local_auth/local_auth/example/lib/main.dart b/packages/local_auth/local_auth/example/lib/main.dart index f2cded002084..146a5d92b29c 100644 --- a/packages/local_auth/local_auth/example/lib/main.dart +++ b/packages/local_auth/local_auth/example/lib/main.dart @@ -183,6 +183,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -196,6 +198,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/local_auth/local_auth/example/pubspec.yaml b/packages/local_auth/local_auth/example/pubspec.yaml index f7dc2fc5b9e7..e02065b6d16f 100644 --- a/packages/local_auth/local_auth/example/pubspec.yaml +++ b/packages/local_auth/local_auth/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index fb6668009ca5..c2d3a007d10b 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.1.4 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index f1ef6a5c797c..521f656bb8ec 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.17 * Adds compatibility with `intl` 0.18.0. diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle index 73d8156110f1..5eecba6278ee 100644 --- a/packages/local_auth/local_auth_android/android/build.gradle +++ b/packages/local_auth/local_auth_android/android/build.gradle @@ -55,9 +55,9 @@ dependencies { api "androidx.biometric:biometric:1.1.0" api "androidx.fragment:fragment:1.5.5" testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'org.robolectric:robolectric:4.5' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/packages/local_auth/local_auth_android/example/lib/main.dart b/packages/local_auth/local_auth_android/example/lib/main.dart index f7d908c81973..f245af973981 100644 --- a/packages/local_auth/local_auth_android/example/lib/main.dart +++ b/packages/local_auth/local_auth_android/example/lib/main.dart @@ -188,6 +188,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -201,6 +203,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/local_auth/local_auth_android/example/pubspec.yaml b/packages/local_auth/local_auth_android/example/pubspec.yaml index c95b89ad0c2a..fddd6b50f815 100644 --- a/packages/local_auth/local_auth_android/example/pubspec.yaml +++ b/packages/local_auth/local_auth_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index 3ad8a4b20a82..aa05acb8a88c 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.17 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_ios/CHANGELOG.md b/packages/local_auth/local_auth_ios/CHANGELOG.md index fd4c918262bb..eca9612fa69e 100644 --- a/packages/local_auth/local_auth_ios/CHANGELOG.md +++ b/packages/local_auth/local_auth_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.12 * Adds compatibility with `intl` 0.18.0. diff --git a/packages/local_auth/local_auth_ios/example/lib/main.dart b/packages/local_auth/local_auth_ios/example/lib/main.dart index 3aa8d6625232..63b317e54c7b 100644 --- a/packages/local_auth/local_auth_ios/example/lib/main.dart +++ b/packages/local_auth/local_auth_ios/example/lib/main.dart @@ -187,6 +187,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -200,6 +202,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/local_auth/local_auth_ios/example/pubspec.yaml b/packages/local_auth/local_auth_ios/example/pubspec.yaml index 720d5a732bd5..21b17fae7288 100644 --- a/packages/local_auth/local_auth_ios/example/pubspec.yaml +++ b/packages/local_auth/local_auth_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_ios/pubspec.yaml b/packages/local_auth/local_auth_ios/pubspec.yaml index c9daa48c1fae..ef2fa7fcdac7 100644 --- a/packages/local_auth/local_auth_ios/pubspec.yaml +++ b/packages/local_auth/local_auth_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.12 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md index 7b9518b57589..be2be0ced788 100644 --- a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md +++ b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.6 * Removes unused `intl` dependency. diff --git a/packages/local_auth/local_auth_platform_interface/pubspec.yaml b/packages/local_auth/local_auth_platform_interface/pubspec.yaml index 70b9d0e5f0b6..bc54978fd3df 100644 --- a/packages/local_auth/local_auth_platform_interface/pubspec.yaml +++ b/packages/local_auth/local_auth_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 1.0.6 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_windows/CHANGELOG.md b/packages/local_auth/local_auth_windows/CHANGELOG.md index b4f2061f2c27..90aa8b6b31db 100644 --- a/packages/local_auth/local_auth_windows/CHANGELOG.md +++ b/packages/local_auth/local_auth_windows/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 1.0.5 + +* Switches internal implementation to Pigeon. + ## 1.0.4 * Updates imports for `prefer_relative_imports`. diff --git a/packages/local_auth/local_auth_windows/example/lib/main.dart b/packages/local_auth/local_auth_windows/example/lib/main.dart index 546b635b8eca..3205cdb81bc8 100644 --- a/packages/local_auth/local_auth_windows/example/lib/main.dart +++ b/packages/local_auth/local_auth_windows/example/lib/main.dart @@ -108,44 +108,6 @@ class _MyAppState extends State { () => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); } - Future _authenticateWithBiometrics() async { - bool authenticated = false; - try { - setState(() { - _isAuthenticating = true; - _authorized = 'Authenticating'; - }); - authenticated = await LocalAuthPlatform.instance.authenticate( - localizedReason: - 'Scan your fingerprint (or face or whatever) to authenticate', - authMessages: [const WindowsAuthMessages()], - options: const AuthenticationOptions( - stickyAuth: true, - biometricOnly: true, - ), - ); - setState(() { - _isAuthenticating = false; - _authorized = 'Authenticating'; - }); - } on PlatformException catch (e) { - print(e); - setState(() { - _isAuthenticating = false; - _authorized = 'Error - ${e.message}'; - }); - return; - } - if (!mounted) { - return; - } - - final String message = authenticated ? 'Authorized' : 'Not Authorized'; - setState(() { - _authorized = message; - }); - } - Future _cancelAuthentication() async { await LocalAuthPlatform.instance.stopAuthentication(); setState(() => _isAuthenticating = false); @@ -188,6 +150,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -201,6 +165,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -209,18 +175,6 @@ class _MyAppState extends State { ], ), ), - ElevatedButton( - onPressed: _authenticateWithBiometrics, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(_isAuthenticating - ? 'Cancel' - : 'Authenticate: biometrics only'), - const Icon(Icons.fingerprint), - ], - ), - ), ], ), ], diff --git a/packages/local_auth/local_auth_windows/example/pubspec.yaml b/packages/local_auth/local_auth_windows/example/pubspec.yaml index 4bb2671f6826..1a1387a0875d 100644 --- a/packages/local_auth/local_auth_windows/example/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart b/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart index b373782c2187..9f918aab0585 100644 --- a/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart +++ b/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart @@ -2,20 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; -import 'types/auth_messages_windows.dart'; + +import 'src/messages.g.dart'; export 'package:local_auth_platform_interface/types/auth_messages.dart'; export 'package:local_auth_platform_interface/types/auth_options.dart'; export 'package:local_auth_platform_interface/types/biometric_type.dart'; export 'package:local_auth_windows/types/auth_messages_windows.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/local_auth_windows'); - /// The implementation of [LocalAuthPlatform] for Windows. class LocalAuthWindows extends LocalAuthPlatform { + /// Creates a new plugin implementation instance. + LocalAuthWindows({ + @visibleForTesting LocalAuthApi? api, + }) : _api = api ?? LocalAuthApi(); + + final LocalAuthApi _api; + /// Registers this class as the default instance of [LocalAuthPlatform]. static void registerWith() { LocalAuthPlatform.instance = LocalAuthWindows(); @@ -28,55 +33,36 @@ class LocalAuthWindows extends LocalAuthPlatform { AuthenticationOptions options = const AuthenticationOptions(), }) async { assert(localizedReason.isNotEmpty); - final Map args = { - 'localizedReason': localizedReason, - 'useErrorDialogs': options.useErrorDialogs, - 'stickyAuth': options.stickyAuth, - 'sensitiveTransaction': options.sensitiveTransaction, - 'biometricOnly': options.biometricOnly, - }; - args.addAll(const WindowsAuthMessages().args); - for (final AuthMessages messages in authMessages) { - if (messages is WindowsAuthMessages) { - args.addAll(messages.args); - } + + if (options.biometricOnly) { + throw UnsupportedError( + "Windows doesn't support the biometricOnly parameter."); } - return (await _channel.invokeMethod('authenticate', args)) ?? false; + + return _api.authenticate(localizedReason); } @override Future deviceSupportsBiometrics() async { - return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? - false; + // Biometrics are supported on any supported device. + return isDeviceSupported(); } @override Future> getEnrolledBiometrics() async { - final List result = (await _channel.invokeListMethod( - 'getEnrolledBiometrics', - )) ?? - []; - final List biometrics = []; - for (final String value in result) { - switch (value) { - case 'weak': - biometrics.add(BiometricType.weak); - break; - case 'strong': - biometrics.add(BiometricType.strong); - break; - } + // Windows doesn't support querying specific biometric types. Since the + // OS considers this a strong authentication API, return weak+strong on + // any supported device. + if (await isDeviceSupported()) { + return [BiometricType.weak, BiometricType.strong]; } - return biometrics; + return []; } @override - Future isDeviceSupported() async => - (await _channel.invokeMethod('isDeviceSupported')) ?? false; + Future isDeviceSupported() async => _api.isDeviceSupported(); /// Always returns false as this method is not supported on Windows. @override - Future stopAuthentication() async { - return false; - } + Future stopAuthentication() async => false; } diff --git a/packages/local_auth/local_auth_windows/lib/src/messages.g.dart b/packages/local_auth/local_auth_windows/lib/src/messages.g.dart new file mode 100644 index 000000000000..312d1c0ba164 --- /dev/null +++ b/packages/local_auth/local_auth_windows/lib/src/messages.g.dart @@ -0,0 +1,81 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class LocalAuthApi { + /// Constructor for [LocalAuthApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + LocalAuthApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + /// Returns true if this device supports authentication. + Future isDeviceSupported() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.LocalAuthApi.isDeviceSupported', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + /// Attempts to authenticate the user with the provided [localizedReason] as + /// the user-facing explanation for the authorization request. + /// + /// Returns true if authorization succeeds, false if it is attempted but is + /// not successful, and an error if authorization could not be attempted. + Future authenticate(String arg_localizedReason) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.LocalAuthApi.authenticate', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_localizedReason]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } +} diff --git a/packages/local_auth/local_auth_windows/pigeons/copyright.txt b/packages/local_auth/local_auth_windows/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/local_auth/local_auth_windows/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/local_auth/local_auth_windows/pigeons/messages.dart b/packages/local_auth/local_auth_windows/pigeons/messages.dart new file mode 100644 index 000000000000..683becdd61fb --- /dev/null +++ b/packages/local_auth/local_auth_windows/pigeons/messages.dart @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + cppOptions: CppOptions(namespace: 'local_auth_windows'), + cppHeaderOut: 'windows/messages.g.h', + cppSourceOut: 'windows/messages.g.cpp', + copyrightHeader: 'pigeons/copyright.txt', +)) +@HostApi() +abstract class LocalAuthApi { + /// Returns true if this device supports authentication. + @async + bool isDeviceSupported(); + + /// Attempts to authenticate the user with the provided [localizedReason] as + /// the user-facing explanation for the authorization request. + /// + /// Returns true if authorization succeeds, false if it is attempted but is + /// not successful, and an error if authorization could not be attempted. + @async + bool authenticate(String localizedReason); +} diff --git a/packages/local_auth/local_auth_windows/pubspec.yaml b/packages/local_auth/local_auth_windows/pubspec.yaml index 9a2effed92ee..9866eef50584 100644 --- a/packages/local_auth/local_auth_windows/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/pubspec.yaml @@ -2,11 +2,11 @@ name: local_auth_windows description: Windows implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.4 +version: 1.0.5 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -24,3 +24,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^5.0.1 diff --git a/packages/local_auth/local_auth_windows/test/local_auth_test.dart b/packages/local_auth/local_auth_windows/test/local_auth_test.dart index b11c19e7b339..917e7b1784b6 100644 --- a/packages/local_auth/local_auth_windows/test/local_auth_test.dart +++ b/packages/local_auth/local_auth_windows/test/local_auth_test.dart @@ -2,78 +2,123 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:local_auth_windows/local_auth_windows.dart'; +import 'package:local_auth_windows/src/messages.g.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - group('authenticate', () { - const MethodChannel channel = MethodChannel( - 'plugins.flutter.io/local_auth_windows', - ); - - final List log = []; - late LocalAuthWindows localAuthentication; + late _FakeLocalAuthApi api; + late LocalAuthWindows plugin; setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall); - switch (methodCall.method) { - case 'getEnrolledBiometrics': - return Future>.value(['weak', 'strong']); - default: - return Future.value(true); - } - }); - localAuthentication = LocalAuthWindows(); - log.clear(); + api = _FakeLocalAuthApi(); + plugin = LocalAuthWindows(api: api); }); - test('authenticate with no arguments passes expected defaults', () async { - await localAuthentication.authenticate( + test('authenticate handles success', () async { + api.returnValue = true; + + final bool result = await plugin.authenticate( authMessages: [const WindowsAuthMessages()], localizedReason: 'My localized reason'); - expect( - log, - [ - isMethodCall('authenticate', - arguments: { - 'localizedReason': 'My localized reason', - 'useErrorDialogs': true, - 'stickyAuth': false, - 'sensitiveTransaction': true, - 'biometricOnly': false, - }..addAll(const WindowsAuthMessages().args)), - ], - ); + + expect(result, true); + expect(api.passedReason, 'My localized reason'); + }); + + test('authenticate handles failure', () async { + api.returnValue = false; + + final bool result = await plugin.authenticate( + authMessages: [const WindowsAuthMessages()], + localizedReason: 'My localized reason'); + + expect(result, false); + expect(api.passedReason, 'My localized reason'); }); - test('authenticate passes all options.', () async { - await localAuthentication.authenticate( - authMessages: [const WindowsAuthMessages()], - localizedReason: 'My localized reason', - options: const AuthenticationOptions( - useErrorDialogs: false, - stickyAuth: true, - sensitiveTransaction: false, - biometricOnly: true, - ), - ); + test('authenticate throws for biometricOnly', () async { expect( - log, - [ - isMethodCall('authenticate', - arguments: { - 'localizedReason': 'My localized reason', - 'useErrorDialogs': false, - 'stickyAuth': true, - 'sensitiveTransaction': false, - 'biometricOnly': true, - }..addAll(const WindowsAuthMessages().args)), - ], - ); + plugin.authenticate( + authMessages: [const WindowsAuthMessages()], + localizedReason: 'My localized reason', + options: const AuthenticationOptions(biometricOnly: true)), + throwsA(isUnsupportedError)); + }); + + test('isDeviceSupported handles supported', () async { + api.returnValue = true; + + final bool result = await plugin.isDeviceSupported(); + + expect(result, true); + }); + + test('isDeviceSupported handles unsupported', () async { + api.returnValue = false; + + final bool result = await plugin.isDeviceSupported(); + + expect(result, false); + }); + + test('deviceSupportsBiometrics handles supported', () async { + api.returnValue = true; + + final bool result = await plugin.deviceSupportsBiometrics(); + + expect(result, true); + }); + + test('deviceSupportsBiometrics handles unsupported', () async { + api.returnValue = false; + + final bool result = await plugin.deviceSupportsBiometrics(); + + expect(result, false); + }); + + test('getEnrolledBiometrics returns expected values when supported', + () async { + api.returnValue = true; + + final List result = await plugin.getEnrolledBiometrics(); + + expect(result, [BiometricType.weak, BiometricType.strong]); + }); + + test('getEnrolledBiometrics returns nothing when unsupported', () async { + api.returnValue = false; + + final List result = await plugin.getEnrolledBiometrics(); + + expect(result, isEmpty); + }); + + test('stopAuthentication returns false', () async { + final bool result = await plugin.stopAuthentication(); + + expect(result, false); }); }); } + +class _FakeLocalAuthApi implements LocalAuthApi { + /// The return value for [isDeviceSupported] and [authenticate]. + bool returnValue = false; + + /// The argument that was passed to [authenticate]. + String? passedReason; + + @override + Future authenticate(String localizedReason) async { + passedReason = localizedReason; + return returnValue; + } + + @override + Future isDeviceSupported() async { + return returnValue; + } +} diff --git a/packages/local_auth/local_auth_windows/windows/CMakeLists.txt b/packages/local_auth/local_auth_windows/windows/CMakeLists.txt index bcf59bb827c7..9784aa5badd9 100644 --- a/packages/local_auth/local_auth_windows/windows/CMakeLists.txt +++ b/packages/local_auth/local_auth_windows/windows/CMakeLists.txt @@ -49,12 +49,14 @@ include_directories(BEFORE SYSTEM ${CMAKE_BINARY_DIR}/include) list(APPEND PLUGIN_SOURCES "local_auth_plugin.cpp" + "local_auth.h" + "messages.g.cpp" + "messages.g.h" ) add_library(${PLUGIN_NAME} SHARED "include/local_auth_windows/local_auth_plugin.h" "local_auth_windows.cpp" - "local_auth.h" ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) diff --git a/packages/local_auth/local_auth_windows/windows/local_auth.h b/packages/local_auth/local_auth_windows/windows/local_auth.h index 94b91f88345a..9cdc6efbcd15 100644 --- a/packages/local_auth/local_auth_windows/windows/local_auth.h +++ b/packages/local_auth/local_auth_windows/windows/local_auth.h @@ -10,8 +10,6 @@ #include #include -#include "include/local_auth_windows/local_auth_plugin.h" - // Include prior to C++/WinRT Headers #include #include @@ -23,6 +21,8 @@ #include #include +#include "messages.g.h" + namespace local_auth_windows { // Abstract class that is used to determine whether a user @@ -50,7 +50,7 @@ class UserConsentVerifier { UserConsentVerifier& operator=(const UserConsentVerifier&) = delete; }; -class LocalAuthPlugin : public flutter::Plugin { +class LocalAuthPlugin : public flutter::Plugin, public LocalAuthApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); @@ -62,28 +62,25 @@ class LocalAuthPlugin : public flutter::Plugin { // Exists for unit testing with mock implementations. LocalAuthPlugin(std::unique_ptr user_consent_verifier); - // Handles method calls from Dart on this plugin's channel. - void HandleMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - virtual ~LocalAuthPlugin(); + // LocalAuthApi: + void IsDeviceSupported( + std::function reply)> result) override; + void Authenticate(const std::string& localized_reason, + std::function reply)> result) override; + private: std::unique_ptr user_consent_verifier_; // Starts authentication process. - winrt::fire_and_forget Authenticate( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - - // Returns enrolled biometric types available on device. - winrt::fire_and_forget GetEnrolledBiometrics( - std::unique_ptr> result); + winrt::fire_and_forget AuthenticateCoroutine( + const std::string& localized_reason, + std::function reply)> result); // Returns whether the system supports Windows Hello. - winrt::fire_and_forget IsDeviceSupported( - std::unique_ptr> result); + winrt::fire_and_forget IsDeviceSupportedCoroutine( + std::function reply)> result); }; -} // namespace local_auth_windows \ No newline at end of file +} // namespace local_auth_windows diff --git a/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp b/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp index 7a25abb53010..80fab37ee50d 100644 --- a/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp +++ b/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp @@ -4,24 +4,10 @@ #include #include "local_auth.h" +#include "messages.g.h" namespace { -template -// Helper method for getting an argument from an EncodableValue. -T GetArgument(const std::string arg, const flutter::EncodableValue* args, - T fallback) { - T result{fallback}; - const auto* arguments = std::get_if(args); - if (arguments) { - auto result_it = arguments->find(flutter::EncodableValue(arg)); - if (result_it != arguments->end()) { - result = std::get(result_it->second); - } - } - return result; -} - // Returns the window's HWND for a given FlutterView. HWND GetRootWindow(flutter::FlutterView* view) { return ::GetAncestor(view->GetNativeWindow(), GA_ROOT); @@ -110,19 +96,9 @@ class UserConsentVerifierImpl : public UserConsentVerifier { // static void LocalAuthPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { - auto channel = - std::make_unique>( - registrar->messenger(), "plugins.flutter.io/local_auth_windows", - &flutter::StandardMethodCodec::GetInstance()); - auto plugin = std::make_unique( [registrar]() { return GetRootWindow(registrar->GetView()); }); - - channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto& call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - + LocalAuthApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } @@ -137,36 +113,22 @@ LocalAuthPlugin::LocalAuthPlugin( LocalAuthPlugin::~LocalAuthPlugin() {} -void LocalAuthPlugin::HandleMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result) { - if (method_call.method_name().compare("authenticate") == 0) { - Authenticate(method_call, std::move(result)); - } else if (method_call.method_name().compare("getEnrolledBiometrics") == 0) { - GetEnrolledBiometrics(std::move(result)); - } else if (method_call.method_name().compare("isDeviceSupported") == 0 || - method_call.method_name().compare("deviceSupportsBiometrics") == - 0) { - IsDeviceSupported(std::move(result)); - } else { - result->NotImplemented(); - } +void LocalAuthPlugin::IsDeviceSupported( + std::function reply)> result) { + IsDeviceSupportedCoroutine(std::move(result)); +} + +void LocalAuthPlugin::Authenticate( + const std::string& localized_reason, + std::function reply)> result) { + AuthenticateCoroutine(localized_reason, std::move(result)); } // Starts authentication process. -winrt::fire_and_forget LocalAuthPlugin::Authenticate( - const flutter::MethodCall& method_call, - std::unique_ptr> result) { - std::wstring reason = Utf16FromUtf8(GetArgument( - "localizedReason", method_call.arguments(), std::string())); - - bool biometric_only = - GetArgument("biometricOnly", method_call.arguments(), false); - if (biometric_only) { - result->Error("biometricOnlyNotSupported", - "Windows doesn't support the biometricOnly parameter."); - co_return; - } +winrt::fire_and_forget LocalAuthPlugin::AuthenticateCoroutine( + const std::string& localized_reason, + std::function reply)> result) { + std::wstring reason = Utf16FromUtf8(localized_reason); winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability ucv_availability = @@ -175,17 +137,19 @@ winrt::fire_and_forget LocalAuthPlugin::Authenticate( if (ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::DeviceNotPresent) { - result->Error("NoHardware", "No biometric hardware found"); + result(FlutterError("NoHardware", "No biometric hardware found")); co_return; } else if (ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::NotConfiguredForUser) { - result->Error("NotEnrolled", "No biometrics enrolled on this device."); + result( + FlutterError("NotEnrolled", "No biometrics enrolled on this device.")); co_return; } else if (ucv_availability != winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::Available) { - result->Error("NotAvailable", "Required security features not enabled"); + result( + FlutterError("NotAvailable", "Required security features not enabled")); co_return; } @@ -195,42 +159,21 @@ winrt::fire_and_forget LocalAuthPlugin::Authenticate( co_await user_consent_verifier_->RequestVerificationForWindowAsync( reason); - result->Success(flutter::EncodableValue( - consent_result == winrt::Windows::Security::Credentials::UI:: - UserConsentVerificationResult::Verified)); + result(consent_result == winrt::Windows::Security::Credentials::UI:: + UserConsentVerificationResult::Verified); } catch (...) { - result->Success(flutter::EncodableValue(false)); - } -} - -// Returns biometric types available on device. -winrt::fire_and_forget LocalAuthPlugin::GetEnrolledBiometrics( - std::unique_ptr> result) { - try { - flutter::EncodableList biometrics; - winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability - ucv_availability = - co_await user_consent_verifier_->CheckAvailabilityAsync(); - if (ucv_availability == winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::Available) { - biometrics.push_back(flutter::EncodableValue("weak")); - biometrics.push_back(flutter::EncodableValue("strong")); - } - result->Success(biometrics); - } catch (const std::exception& e) { - result->Error("no_biometrics_available", e.what()); + result(false); } } // Returns whether the device supports Windows Hello or not. -winrt::fire_and_forget LocalAuthPlugin::IsDeviceSupported( - std::unique_ptr> result) { +winrt::fire_and_forget LocalAuthPlugin::IsDeviceSupportedCoroutine( + std::function reply)> result) { winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability ucv_availability = co_await user_consent_verifier_->CheckAvailabilityAsync(); - result->Success(flutter::EncodableValue( - ucv_availability == winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::Available)); + result(ucv_availability == winrt::Windows::Security::Credentials::UI:: + UserConsentVerifierAvailability::Available); } } // namespace local_auth_windows diff --git a/packages/local_auth/local_auth_windows/windows/messages.g.cpp b/packages/local_auth/local_auth_windows/windows/messages.g.cpp new file mode 100644 index 000000000000..e44b17c6a38d --- /dev/null +++ b/packages/local_auth/local_auth_windows/windows/messages.g.cpp @@ -0,0 +1,110 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace local_auth_windows { +/// The codec used by LocalAuthApi. +const flutter::StandardMessageCodec& LocalAuthApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &flutter::StandardCodecSerializer::GetInstance()); +} + +// Sets up an instance of `LocalAuthApi` to handle messages through the +// `binary_messenger`. +void LocalAuthApi::SetUp(flutter::BinaryMessenger* binary_messenger, + LocalAuthApi* api) { + { + auto channel = + std::make_unique>( + binary_messenger, + "dev.flutter.pigeon.LocalAuthApi.isDeviceSupported", &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->IsDeviceSupported([reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back( + flutter::EncodableValue(std::move(output).TakeValue())); + reply(flutter::EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } + { + auto channel = + std::make_unique>( + binary_messenger, "dev.flutter.pigeon.LocalAuthApi.authenticate", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_localized_reason_arg = args.at(0); + if (encodable_localized_reason_arg.IsNull()) { + reply(WrapError("localized_reason_arg unexpectedly null.")); + return; + } + const auto& localized_reason_arg = + std::get(encodable_localized_reason_arg); + api->Authenticate( + localized_reason_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back( + flutter::EncodableValue(std::move(output).TakeValue())); + reply(flutter::EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } +} + +flutter::EncodableValue LocalAuthApi::WrapError( + std::string_view error_message) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(std::string(error_message)), + flutter::EncodableValue("Error"), flutter::EncodableValue()}); +} +flutter::EncodableValue LocalAuthApi::WrapError(const FlutterError& error) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(error.message()), + flutter::EncodableValue(error.code()), error.details()}); +} + +} // namespace local_auth_windows diff --git a/packages/local_auth/local_auth_windows/windows/messages.g.h b/packages/local_auth/local_auth_windows/windows/messages.g.h new file mode 100644 index 000000000000..2ceff7732c90 --- /dev/null +++ b/packages/local_auth/local_auth_windows/windows/messages.g.h @@ -0,0 +1,93 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_LOCAL_AUTH_WINDOWS_H_ +#define PIGEON_LOCAL_AUTH_WINDOWS_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace local_auth_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + explicit FlutterError(const std::string& code, const std::string& message) + : code_(code), message_(message) {} + explicit FlutterError(const std::string& code, const std::string& message, + const flutter::EncodableValue& details) + : code_(code), message_(message), details_(details) {} + + const std::string& code() const { return code_; } + const std::string& message() const { return message_; } + const flutter::EncodableValue& details() const { return details_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) { new (&v_) T(rhs); } + ErrorOr(const T&& rhs) { v_ = std::move(rhs); } + ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); } + ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); } + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class LocalAuthApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class LocalAuthApi { + public: + LocalAuthApi(const LocalAuthApi&) = delete; + LocalAuthApi& operator=(const LocalAuthApi&) = delete; + virtual ~LocalAuthApi(){}; + // Returns true if this device supports authentication. + virtual void IsDeviceSupported( + std::function reply)> result) = 0; + // Attempts to authenticate the user with the provided [localizedReason] as + // the user-facing explanation for the authorization request. + // + // Returns true if authorization succeeds, false if it is attempted but is + // not successful, and an error if authorization could not be attempted. + virtual void Authenticate( + const std::string& localized_reason, + std::function reply)> result) = 0; + + // The codec used by LocalAuthApi. + static const flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `LocalAuthApi` to handle messages through the + // `binary_messenger`. + static void SetUp(flutter::BinaryMessenger* binary_messenger, + LocalAuthApi* api); + static flutter::EncodableValue WrapError(std::string_view error_message); + static flutter::EncodableValue WrapError(const FlutterError& error); + + protected: + LocalAuthApi() = default; +}; +} // namespace local_auth_windows +#endif // PIGEON_LOCAL_AUTH_WINDOWS_H_ diff --git a/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp b/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp index 3828b05eef07..6b1b0ed79c3f 100644 --- a/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp +++ b/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp @@ -4,10 +4,6 @@ #include "include/local_auth_windows/local_auth_plugin.h" -#include -#include -#include -#include #include #include #include @@ -32,9 +28,6 @@ using ::testing::Pointee; using ::testing::Return; TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierAvailable) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -48,48 +41,14 @@ TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierAvailable) { }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(false); + plugin.IsDeviceSupported([&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); - - plugin.HandleMethodCall( - flutter::MethodCall("isDeviceSupported", - std::make_unique()), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_TRUE(result.value()); } TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierNotAvailable) { - std::unique_ptr result = - std::make_unique(); - - std::unique_ptr mockConsentVerifier = - std::make_unique(); - - EXPECT_CALL(*mockConsentVerifier, CheckAvailabilityAsync) - .Times(1) - .WillOnce([]() -> winrt::Windows::Foundation::IAsyncOperation< - winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability> { - co_return winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::DeviceNotPresent; - }); - - LocalAuthPlugin plugin(std::move(mockConsentVerifier)); - - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); - - plugin.HandleMethodCall( - flutter::MethodCall("isDeviceSupported", - std::make_unique()), - std::move(result)); -} - -TEST(LocalAuthPlugin, - GetEnrolledBiometricsHandlerReturnEmptyListIfVerifierNotAvailable) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -103,72 +62,14 @@ TEST(LocalAuthPlugin, }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(true); + plugin.IsDeviceSupported([&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableList()))); - - plugin.HandleMethodCall( - flutter::MethodCall("getEnrolledBiometrics", - std::make_unique()), - std::move(result)); -} - -TEST(LocalAuthPlugin, - GetEnrolledBiometricsHandlerReturnNonEmptyListIfVerifierAvailable) { - std::unique_ptr result = - std::make_unique(); - - std::unique_ptr mockConsentVerifier = - std::make_unique(); - - EXPECT_CALL(*mockConsentVerifier, CheckAvailabilityAsync) - .Times(1) - .WillOnce([]() -> winrt::Windows::Foundation::IAsyncOperation< - winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability> { - co_return winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::Available; - }); - - LocalAuthPlugin plugin(std::move(mockConsentVerifier)); - - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, - SuccessInternal(Pointee(EncodableList( - {EncodableValue("weak"), EncodableValue("strong")})))); - - plugin.HandleMethodCall( - flutter::MethodCall("getEnrolledBiometrics", - std::make_unique()), - std::move(result)); -} - -TEST(LocalAuthPlugin, AuthenticateHandlerDoesNotSupportBiometricOnly) { - std::unique_ptr result = - std::make_unique(); - - std::unique_ptr mockConsentVerifier = - std::make_unique(); - - LocalAuthPlugin plugin(std::move(mockConsentVerifier)); - - EXPECT_CALL(*result, ErrorInternal).Times(1); - EXPECT_CALL(*result, SuccessInternal).Times(0); - - std::unique_ptr args = - std::make_unique(EncodableMap({ - {"localizedReason", EncodableValue("My Reason")}, - {"biometricOnly", EncodableValue(true)}, - })); - - plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenAuthorized) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -193,24 +94,15 @@ TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenAuthorized) { }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(false); + plugin.Authenticate("My Reason", + [&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); - - std::unique_ptr args = - std::make_unique(EncodableMap({ - {"localizedReason", EncodableValue("My Reason")}, - {"biometricOnly", EncodableValue(false)}, - })); - - plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_TRUE(result.value()); } TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenNotAuthorized) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -235,18 +127,12 @@ TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenNotAuthorized) { }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(true); + plugin.Authenticate("My Reason", + [&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); - - std::unique_ptr args = - std::make_unique(EncodableMap({ - {"localizedReason", EncodableValue("My Reason")}, - {"biometricOnly", EncodableValue(false)}, - })); - - plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } } // namespace test diff --git a/packages/local_auth/local_auth_windows/windows/test/mocks.h b/packages/local_auth/local_auth_windows/windows/test/mocks.h index d82ae801b4b9..a31eb98aa7ef 100644 --- a/packages/local_auth/local_auth_windows/windows/test/mocks.h +++ b/packages/local_auth/local_auth_windows/windows/test/mocks.h @@ -5,10 +5,6 @@ #ifndef PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ #define PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ -#include -#include -#include -#include #include #include @@ -19,23 +15,8 @@ namespace test { namespace { -using flutter::EncodableMap; -using flutter::EncodableValue; using ::testing::_; -class MockMethodResult : public flutter::MethodResult<> { - public: - ~MockMethodResult() = default; - - MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result), - (override)); - MOCK_METHOD(void, ErrorInternal, - (const std::string& error_code, const std::string& error_message, - const EncodableValue* details), - (override)); - MOCK_METHOD(void, NotImplementedInternal, (), (override)); -}; - class MockUserConsentVerifier : public UserConsentVerifier { public: explicit MockUserConsentVerifier(){}; diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md index 436523551924..0f5e8e6d7225 100644 --- a/packages/path_provider/path_provider/CHANGELOG.md +++ b/packages/path_provider/path_provider/CHANGELOG.md @@ -1,5 +1,11 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 2.0.12 + +* Switches to the new `path_provider_foundation` implementation package + for iOS and macOS. * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md index 3a52e3e72050..6a954d2ece61 100644 --- a/packages/path_provider/path_provider/README.md +++ b/packages/path_provider/path_provider/README.md @@ -36,7 +36,7 @@ Directories support by platform: | External Storage | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | External Cache Directories | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | External Storage Directories | ✔️ | ❌ | ❌ | ❌️ | ❌️ | -| Downloads | ❌ | ❌ | ✔️ | ✔️ | ✔️ | +| Downloads | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ## Testing diff --git a/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart index bf150f66f49b..f59a8faf31e0 100644 --- a/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart +++ b/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart @@ -87,19 +87,16 @@ void main() { } testWidgets('getDownloadsDirectory', (WidgetTester tester) async { - if (Platform.isIOS || Platform.isAndroid) { + if (Platform.isAndroid) { final Future result = getDownloadsDirectory(); expect(result, throwsA(isInstanceOf())); } else { final Directory? result = await getDownloadsDirectory(); - if (Platform.isMacOS) { - // On recent versions of macOS, actually using the downloads directory - // requires a user prompt, so will fail on CI. Instead, just check that - // it returned a path with the expected directory name. - expect(result?.path, endsWith('Downloads')); - } else { - _verifySampleFile(result, 'downloads'); - } + // On recent versions of macOS, actually using the downloads directory + // requires a user prompt (so will fail on CI), and on some platforms the + // directory may not exist. Instead of verifying that it exists, just + // check that it returned a path. + expect(result?.path, isNotEmpty); } }); } diff --git a/packages/path_provider/path_provider/example/pubspec.yaml b/packages/path_provider/path_provider/example/pubspec.yaml index 5964a267f96d..ffb878bcf146 100644 --- a/packages/path_provider/path_provider/example/pubspec.yaml +++ b/packages/path_provider/path_provider/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider/lib/path_provider.dart b/packages/path_provider/path_provider/lib/path_provider.dart index e89d29dc0036..b58a7ff6cc7b 100644 --- a/packages/path_provider/path_provider/lib/path_provider.dart +++ b/packages/path_provider/path_provider/lib/path_provider.dart @@ -45,11 +45,11 @@ PathProviderPlatform get _platform => PathProviderPlatform.instance; /// (and cleaning up) files or directories within this directory. This /// directory is scoped to the calling application. /// -/// On iOS, this uses the `NSCachesDirectory` API. +/// Example implementations: +/// - `NSCachesDirectory` on iOS and macOS. +/// - `Context.getCacheDir` on Android. /// -/// On Android, this uses the `getCacheDir` API on the context. -/// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getTemporaryDirectory() async { final String? path = await _platform.getTemporaryPath(); @@ -63,15 +63,16 @@ Future getTemporaryDirectory() async { /// Path to a directory where the application may place application support /// files. /// +/// If this directory does not exist, it is created automatically. +/// /// Use this for files you don’t want exposed to the user. Your app should not /// use this directory for user data files. /// -/// On iOS, this uses the `NSApplicationSupportDirectory` API. -/// If this directory does not exist, it is created automatically. -/// -/// On Android, this function uses the `getFilesDir` API on the context. +/// Example implementations: +/// - `NSApplicationSupportDirectory` on iOS and macOS. +/// - The Flutter engine's `PathUtils.getFilesDir` API on Android. /// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getApplicationSupportDirectory() async { final String? path = await _platform.getApplicationSupportPath(); @@ -86,10 +87,14 @@ Future getApplicationSupportDirectory() async { /// Path to the directory where application can store files that are persistent, /// backed up, and not visible to the user, such as sqlite.db. /// -/// On Android, this function throws an [UnsupportedError] as no equivalent -/// path exists. +/// Example implementations: +/// - `NSApplicationSupportDirectory` on iOS and macOS. +/// +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. For example, this is unlikely to ever be supported on Android, +/// as no equivalent path exists. /// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory on a supported platform. Future getLibraryDirectory() async { final String? path = await _platform.getLibraryPath(); @@ -102,14 +107,14 @@ Future getLibraryDirectory() async { /// Path to a directory where the application may place data that is /// user-generated, or that cannot otherwise be recreated by your application. /// -/// On iOS, this uses the `NSDocumentDirectory` API. Consider using -/// [getApplicationSupportDirectory] instead if the data is not user-generated. +/// Consider using another path, such as [getApplicationSupportDirectory] or +/// [getExternalStorageDirectory], if the data is not user-generated. /// -/// On Android, this uses the `getDataDirectory` API on the context. Consider -/// using [getExternalStorageDirectory] instead if data is intended to be visible -/// to the user. +/// Example implementations: +/// - `NSDocumentDirectory` on iOS and macOS. +/// - The Flutter engine's `PathUtils.getDataDirectory` API on Android. /// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getApplicationDocumentsDirectory() async { final String? path = await _platform.getApplicationDocumentsPath(); @@ -121,13 +126,13 @@ Future getApplicationDocumentsDirectory() async { } /// Path to a directory where the application may access top level storage. -/// The current operating system should be determined before issuing this -/// function call, as this functionality is only available on Android. /// -/// On iOS, this function throws an [UnsupportedError] as it is not possible -/// to access outside the app's sandbox. +/// Example implementation: +/// - `getExternalFilesDir(null)` on Android. /// -/// On Android this uses the `getExternalFilesDir(null)`. +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform (for example, on iOS where it is not possible to access outside +/// the app's sandbox). Future getExternalStorageDirectory() async { final String? path = await _platform.getExternalStoragePath(); if (path == null) { @@ -136,19 +141,19 @@ Future getExternalStorageDirectory() async { return Directory(path); } -/// Paths to directories where application specific external cache data can be -/// stored. These paths typically reside on external storage like separate -/// partitions or SD cards. Phones may have multiple storage directories -/// available. +/// Paths to directories where application specific cache data can be stored +/// externally. /// -/// The current operating system should be determined before issuing this -/// function call, as this functionality is only available on Android. +/// These paths typically reside on external storage like separate partitions +/// or SD cards. Phones may have multiple storage directories available. /// -/// On iOS, this function throws an UnsupportedError as it is not possible -/// to access outside the app's sandbox. +/// Example implementation: +/// - Context.getExternalCacheDirs() on Android (or +/// Context.getExternalCacheDir() on API levels below 19). /// -/// On Android this returns Context.getExternalCacheDirs() or -/// Context.getExternalCacheDir() on API levels below 19. +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. This is unlikely to ever be supported on any platform other than +/// Android. Future?> getExternalCacheDirectories() async { final List? paths = await _platform.getExternalCachePaths(); if (paths == null) { @@ -158,18 +163,19 @@ Future?> getExternalCacheDirectories() async { return paths.map((String path) => Directory(path)).toList(); } -/// Paths to directories where application specific data can be stored. +/// Paths to directories where application specific data can be stored +/// externally. +/// /// These paths typically reside on external storage like separate partitions /// or SD cards. Phones may have multiple storage directories available. /// -/// The current operating system should be determined before issuing this -/// function call, as this functionality is only available on Android. +/// Example implementation: +/// - Context.getExternalFilesDirs(type) on Android (or +/// Context.getExternalFilesDir(type) on API levels below 19). /// -/// On iOS, this function throws an UnsupportedError as it is not possible -/// to access outside the app's sandbox. -/// -/// On Android this returns Context.getExternalFilesDirs(String type) or -/// Context.getExternalFilesDir(String type) on API levels below 19. +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. This is unlikely to ever be supported on any platform other than +/// Android. Future?> getExternalStorageDirectories({ /// Optional parameter. See [StorageDirectory] for more informations on /// how this type translates to Android storage directories. @@ -185,10 +191,12 @@ Future?> getExternalStorageDirectories({ } /// Path to the directory where downloaded files can be stored. -/// This is typically only relevant on desktop operating systems. /// -/// On Android and on iOS, this function throws an [UnsupportedError] as no equivalent -/// path exists. +/// The returned directory is not guaranteed to exist, so clients should verify +/// that it does before using it, and potentially create it if necessary. +/// +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. Future getDownloadsDirectory() async { final String? path = await _platform.getDownloadsPath(); if (path == null) { diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index 8b68e1264fe7..8c139ccbb87b 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -2,11 +2,11 @@ name: path_provider description: Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories. repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.0.11 +version: 2.0.12 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -14,11 +14,11 @@ flutter: android: default_package: path_provider_android ios: - default_package: path_provider_ios - macos: - default_package: path_provider_macos + default_package: path_provider_foundation linux: default_package: path_provider_linux + macos: + default_package: path_provider_foundation windows: default_package: path_provider_windows @@ -26,9 +26,8 @@ dependencies: flutter: sdk: flutter path_provider_android: ^2.0.6 - path_provider_ios: ^2.0.6 + path_provider_foundation: ^2.1.0 path_provider_linux: ^2.0.1 - path_provider_macos: ^2.0.0 path_provider_platform_interface: ^2.0.0 path_provider_windows: ^2.0.2 diff --git a/packages/path_provider/path_provider_android/CHANGELOG.md b/packages/path_provider/path_provider_android/CHANGELOG.md index 4edf0f8d290f..acf99b7a5e25 100644 --- a/packages/path_provider/path_provider_android/CHANGELOG.md +++ b/packages/path_provider/path_provider_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.22 * Removes unused Guava dependency. diff --git a/packages/path_provider/path_provider_android/example/pubspec.yaml b/packages/path_provider/path_provider_android/example/pubspec.yaml index b460d6ba49ce..e53c44ffda68 100644 --- a/packages/path_provider/path_provider_android/example/pubspec.yaml +++ b/packages/path_provider/path_provider_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml index 9a8df83f4922..dcdf938feee5 100644 --- a/packages/path_provider/path_provider_android/pubspec.yaml +++ b/packages/path_provider/path_provider_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.22 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_macos/.gitignore b/packages/path_provider/path_provider_foundation/.gitignore similarity index 100% rename from packages/path_provider/path_provider_macos/.gitignore rename to packages/path_provider/path_provider_foundation/.gitignore diff --git a/packages/path_provider/path_provider_ios/AUTHORS b/packages/path_provider/path_provider_foundation/AUTHORS similarity index 100% rename from packages/path_provider/path_provider_ios/AUTHORS rename to packages/path_provider/path_provider_foundation/AUTHORS diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md new file mode 100644 index 000000000000..7adb04f4c984 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md @@ -0,0 +1,13 @@ +## NEXT + +* Updates minimum supported Flutter version to 3.0. + +## 2.1.1 + +* Fixes a regression in the path retured by `getApplicationSupportDirectory` on iOS. + +## 2.1.0 + +* Renames the package previously published as + [`path_provider_macos`](https://pub.dev/packages/path_provider_macos) +* Adds iOS support. diff --git a/packages/path_provider/path_provider_ios/LICENSE b/packages/path_provider/path_provider_foundation/LICENSE similarity index 100% rename from packages/path_provider/path_provider_ios/LICENSE rename to packages/path_provider/path_provider_foundation/LICENSE diff --git a/packages/path_provider/path_provider_ios/README.md b/packages/path_provider/path_provider_foundation/README.md similarity index 78% rename from packages/path_provider/path_provider_ios/README.md rename to packages/path_provider/path_provider_foundation/README.md index dcfa06dcdfb0..474244b27aea 100644 --- a/packages/path_provider/path_provider_ios/README.md +++ b/packages/path_provider/path_provider_foundation/README.md @@ -1,6 +1,6 @@ -# path\_provider\_ios +# path\_provider\_foundation -The iOS implementation of [`path_provider`][1]. +The iOS and macOS implementation of [`path_provider`][1]. ## Usage diff --git a/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift new file mode 100644 index 000000000000..af043090f545 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift @@ -0,0 +1,67 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Foundation + +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#endif + +public class PathProviderPlugin: NSObject, FlutterPlugin, PathProviderApi { + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = PathProviderPlugin() + // Workaround for https://github.com/flutter/flutter/issues/118103. +#if os(iOS) + let messenger = registrar.messenger() +#else + let messenger = registrar.messenger +#endif + PathProviderApiSetup.setUp(binaryMessenger: messenger, api: instance) + } + + func getDirectoryPath(type: DirectoryType) -> String? { + var path = getDirectory(ofType: fileManagerDirectoryForType(type)) + #if os(macOS) + // In a non-sandboxed app, this is a shared directory where applications are + // expected to use its bundle ID as a subdirectory. (For non-sandboxed apps, + // adding the extra path is harmless). + // This is not done for iOS, for compatibility with older versions of the + // plugin. + if type == .applicationSupport { + if let basePath = path { + let basePathURL = URL.init(fileURLWithPath: basePath) + path = basePathURL.appendingPathComponent(Bundle.main.bundleIdentifier!).path + } + } + #endif + return path + } +} + +/// Returns the FileManager constant corresponding to the given type. +private func fileManagerDirectoryForType(_ type: DirectoryType) -> FileManager.SearchPathDirectory { + switch type { + case .applicationDocuments: + return FileManager.SearchPathDirectory.documentDirectory + case .applicationSupport: + return FileManager.SearchPathDirectory.applicationSupportDirectory + case .downloads: + return FileManager.SearchPathDirectory.downloadsDirectory + case .library: + return FileManager.SearchPathDirectory.libraryDirectory + case .temp: + return FileManager.SearchPathDirectory.cachesDirectory + } +} + +/// Returns the user-domain directory of the given type. +private func getDirectory(ofType directory: FileManager.SearchPathDirectory) -> String? { + let paths = NSSearchPathForDirectoriesInDomains( + directory, + FileManager.SearchPathDomainMask.userDomainMask, + true) + return paths.first +} diff --git a/packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift b/packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift new file mode 100644 index 000000000000..08ab62aafdb7 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift @@ -0,0 +1,60 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#else +#error("Unsupported platform.") +#endif + + +/// Generated class from Pigeon. + +enum DirectoryType: Int { + case applicationDocuments = 0 + case applicationSupport = 1 + case downloads = 2 + case library = 3 + case temp = 4 +} +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol PathProviderApi { + func getDirectoryPath(type: DirectoryType) -> String? +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class PathProviderApiSetup { + /// The codec used by PathProviderApi. + /// Sets up an instance of `PathProviderApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PathProviderApi?) { + let getDirectoryPathChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.PathProviderApi.getDirectoryPath", binaryMessenger: binaryMessenger) + if let api = api { + getDirectoryPathChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let typeArg = DirectoryType(rawValue: args[0] as! Int)! + let result = api.getDirectoryPath(type: typeArg) + reply(wrapResult(result)) + } + } else { + getDirectoryPathChannel.setMessageHandler(nil) + } + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: FlutterError) -> [Any?] { + return [ + error.code, + error.message, + error.details + ] +} diff --git a/packages/path_provider/path_provider_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift similarity index 60% rename from packages/path_provider/path_provider_macos/example/macos/RunnerTests/RunnerTests.swift rename to packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift index 35704cdb06d8..99a56f2bfebf 100644 --- a/packages/path_provider/path_provider_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift @@ -2,20 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import FlutterMacOS import XCTest -import path_provider_macos +@testable import path_provider_foundation + +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#endif class RunnerTests: XCTestCase { func testGetTemporaryDirectory() throws { let plugin = PathProviderPlugin() - var path: String? - plugin.handle( - FlutterMethodCall(methodName: "getTemporaryDirectory", arguments: nil), - result: { (result: Any?) -> Void in - path = result as? String - - }) + let path = plugin.getDirectoryPath(type: .temp) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( @@ -27,13 +26,7 @@ class RunnerTests: XCTestCase { func testGetApplicationDocumentsDirectory() throws { let plugin = PathProviderPlugin() - var path: String? - plugin.handle( - FlutterMethodCall(methodName: "getApplicationDocumentsDirectory", arguments: nil), - result: { (result: Any?) -> Void in - path = result as? String - - }) + let path = plugin.getDirectoryPath(type: .applicationDocuments) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( @@ -45,15 +38,20 @@ class RunnerTests: XCTestCase { func testGetApplicationSupportDirectory() throws { let plugin = PathProviderPlugin() - var path: String? - plugin.handle( - FlutterMethodCall(methodName: "getApplicationSupportDirectory", arguments: nil), - result: { (result: Any?) -> Void in - path = result as? String - - }) - // The application support directory path should be the system application support - // path with an added subdirectory based on the app name. + let path = plugin.getDirectoryPath(type: .applicationSupport) +#if os(iOS) + // On iOS, the application support directory path should be just the system application + // support path. + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.applicationSupportDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) +#else + // On macOS, the application support directory path should be the system application + // support path with an added subdirectory based on the app name. XCTAssert( path!.hasPrefix( NSSearchPathForDirectoriesInDomains( @@ -62,17 +60,12 @@ class RunnerTests: XCTestCase { true ).first!)) XCTAssert(path!.hasSuffix("Example")) +#endif } func testGetLibraryDirectory() throws { let plugin = PathProviderPlugin() - var path: String? - plugin.handle( - FlutterMethodCall(methodName: "getLibraryDirectory", arguments: nil), - result: { (result: Any?) -> Void in - path = result as? String - - }) + let path = plugin.getDirectoryPath(type: .library) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( @@ -84,13 +77,7 @@ class RunnerTests: XCTestCase { func testGetDownloadsDirectory() throws { let plugin = PathProviderPlugin() - var path: String? - plugin.handle( - FlutterMethodCall(methodName: "getDownloadsDirectory", arguments: nil), - result: { (result: Any?) -> Void in - path = result as? String - - }) + let path = plugin.getDirectoryPath(type: .downloads) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( diff --git a/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec new file mode 100644 index 000000000000..36093b567fb9 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec @@ -0,0 +1,25 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'path_provider_foundation' + s.version = '0.0.1' + s.summary = 'An iOS and macOS implementation of the path_provider plugin.' + s.description = <<-DESC + An iOS and macOS implementation of the Flutter plugin for getting commonly used locations on the filesystem. + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_foundation' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_foundation' } + s.source_files = 'Classes/**/*' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.11' + s.ios.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } + s.swift_version = '5.0' +end diff --git a/packages/path_provider/path_provider_ios/example/README.md b/packages/path_provider/path_provider_foundation/example/README.md similarity index 100% rename from packages/path_provider/path_provider_ios/example/README.md rename to packages/path_provider/path_provider_foundation/example/README.md diff --git a/packages/path_provider/path_provider_macos/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart similarity index 100% rename from packages/path_provider/path_provider_macos/example/integration_test/path_provider_test.dart rename to packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/.gitignore b/packages/path_provider/path_provider_foundation/example/ios/.gitignore similarity index 95% rename from packages/shared_preferences/shared_preferences_ios/example/ios/.gitignore rename to packages/path_provider/path_provider_foundation/example/ios/.gitignore index e96ef602b8d1..7a7f9873ad7d 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/.gitignore +++ b/packages/path_provider/path_provider_foundation/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/path_provider/path_provider_ios/example/ios/Flutter/AppFrameworkInfo.plist b/packages/path_provider/path_provider_foundation/example/ios/Flutter/AppFrameworkInfo.plist similarity index 86% rename from packages/path_provider/path_provider_ios/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/path_provider/path_provider_foundation/example/ios/Flutter/AppFrameworkInfo.plist index 3a9c234f96d4..9625e105df39 100644 --- a/packages/path_provider/path_provider_ios/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/path_provider/path_provider_foundation/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,11 +20,7 @@ ???? CFBundleVersion 1.0 - UIRequiredDeviceCapabilities - - arm64 - MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/Debug.xcconfig b/packages/path_provider/path_provider_foundation/example/ios/Flutter/Debug.xcconfig similarity index 58% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/Debug.xcconfig rename to packages/path_provider/path_provider_foundation/example/ios/Flutter/Debug.xcconfig index d0eccdcaf401..ec97fc6f3021 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/Debug.xcconfig +++ b/packages/path_provider/path_provider_foundation/example/ios/Flutter/Debug.xcconfig @@ -1,3 +1,2 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/Release.xcconfig b/packages/path_provider/path_provider_foundation/example/ios/Flutter/Release.xcconfig similarity index 58% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/Release.xcconfig rename to packages/path_provider/path_provider_foundation/example/ios/Flutter/Release.xcconfig index c751c1d022fa..c4855bfe2000 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/Release.xcconfig +++ b/packages/path_provider/path_provider_foundation/example/ios/Flutter/Release.xcconfig @@ -1,3 +1,2 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" diff --git a/packages/path_provider/path_provider_ios/example/ios/Podfile b/packages/path_provider/path_provider_foundation/example/ios/Podfile similarity index 95% rename from packages/path_provider/path_provider_ios/example/ios/Podfile rename to packages/path_provider/path_provider_foundation/example/ios/Podfile index 3924e59aa0f9..211fcba3d000 100644 --- a/packages/path_provider/path_provider_ios/example/ios/Podfile +++ b/packages/path_provider/path_provider_foundation/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -28,7 +28,11 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + use_frameworks! + use_modular_headers! + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do inherit! :search_paths end diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj similarity index 60% rename from packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj index 601985b46ae6..70cdc7657d6d 100644 --- a/packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,24 +3,23 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ - 2D9222481EC32A19007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D9222471EC32A19007564B0 /* GeneratedPluginRegistrant.m */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 33258D7929818305006BAA98 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33258D7729818302006BAA98 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 60774162343BF6F19B3D65CE /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E2EF24BBF807F7F7B95F2B9 /* libPods-RunnerTests.a */; }; - 85DDFCF6BBDEE02B9D9F8138 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 569E86265D93B926F433B2DF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 479D5DD53D431F6BBABA2E43 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - F76AC1DF26671E960040C8BC /* PathProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1DE26671E960040C8BC /* PathProviderTests.m */; }; + D18DAAE2A3406D4789C8DAB2 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 988A82A3033B36B9EAF2782B /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - F76AC1E126671E960040C8BC /* PBXContainerItemProxy */ = { + 3380327829784D96002D32AE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; @@ -43,60 +42,57 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0E2EF24BBF807F7F7B95F2B9 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 29F2567B3AE74A9113ED3394 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 2D9222461EC32A19007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 2D9222471EC32A19007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1E28C831B7D8EA9408BFB69A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 33258D7729818302006BAA98 /* RunnerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RunnerTests.swift; path = ../../darwin/RunnerTests/RunnerTests.swift; sourceTree = ""; }; + 3380327429784D96002D32AE /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 694A199F61914F41AAFD0B7F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 6EB685EA3DDA2EED39600D11 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 479D5DD53D431F6BBABA2E43 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5DB8EF5A2759054360D79B8D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 86F7986E9DC17432CC8AE464 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 91DA83C3D33EB641BAEA3087 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - D317CA1E83064E01753D8BB5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - F76AC1DC26671E960040C8BC /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - F76AC1DE26671E960040C8BC /* PathProviderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PathProviderTests.m; sourceTree = ""; }; - F76AC1E026671E960040C8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 988A82A3033B36B9EAF2782B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B0CB6DC5569DDEB858FBEB22 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C1E50EBAA845915BAF5591C9 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { + 3380327129784D96002D32AE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 85DDFCF6BBDEE02B9D9F8138 /* libPods-Runner.a in Frameworks */, + D18DAAE2A3406D4789C8DAB2 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - F76AC1D926671E960040C8BC /* Frameworks */ = { + 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 60774162343BF6F19B3D65CE /* libPods-RunnerTests.a in Frameworks */, + 569E86265D93B926F433B2DF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + 33258D76298182CC006BAA98 /* RunnerTests */ = { isa = PBXGroup; children = ( - 694A199F61914F41AAFD0B7F /* Pods-Runner.debug.xcconfig */, - D317CA1E83064E01753D8BB5 /* Pods-Runner.release.xcconfig */, - 6EB685EA3DDA2EED39600D11 /* Pods-RunnerTests.debug.xcconfig */, - 29F2567B3AE74A9113ED3394 /* Pods-RunnerTests.release.xcconfig */, + 33258D7729818302006BAA98 /* RunnerTests.swift */, ); - name = Pods; + name = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -115,10 +111,10 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, - F76AC1DD26671E960040C8BC /* RunnerTests */, + 33258D76298182CC006BAA98 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, - 840012C8B5EDBCF56B0E4AC1 /* Pods */, - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + E1C876D20454FC3A1ED7F7E5 /* Pods */, + C72F144CE69E83C4574EB334 /* Frameworks */, ); sourceTree = ""; }; @@ -126,7 +122,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, - F76AC1DC26671E960040C8BC /* RunnerTests.xctest */, + 3380327429784D96002D32AE /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -134,59 +130,74 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 2D9222461EC32A19007564B0 /* GeneratedPluginRegistrant.h */, - 2D9222471EC32A19007564B0 /* GeneratedPluginRegistrant.m */, - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + C72F144CE69E83C4574EB334 /* Frameworks */ = { isa = PBXGroup; children = ( - C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */, - 0E2EF24BBF807F7F7B95F2B9 /* libPods-RunnerTests.a */, + 479D5DD53D431F6BBABA2E43 /* Pods_Runner.framework */, + 988A82A3033B36B9EAF2782B /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; - F76AC1DD26671E960040C8BC /* RunnerTests */ = { + E1C876D20454FC3A1ED7F7E5 /* Pods */ = { isa = PBXGroup; children = ( - F76AC1DE26671E960040C8BC /* PathProviderTests.m */, - F76AC1E026671E960040C8BC /* Info.plist */, - ); - path = RunnerTests; + 5DB8EF5A2759054360D79B8D /* Pods-Runner.debug.xcconfig */, + B0CB6DC5569DDEB858FBEB22 /* Pods-Runner.release.xcconfig */, + 91DA83C3D33EB641BAEA3087 /* Pods-Runner.profile.xcconfig */, + 1E28C831B7D8EA9408BFB69A /* Pods-RunnerTests.debug.xcconfig */, + C1E50EBAA845915BAF5591C9 /* Pods-RunnerTests.release.xcconfig */, + 86F7986E9DC17432CC8AE464 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 3380327329784D96002D32AE /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3380327D29784D96002D32AE /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 9144B1C9B36C0B00C1DF8FBB /* [CP] Check Pods Manifest.lock */, + 3380327029784D96002D32AE /* Sources */, + 3380327129784D96002D32AE /* Frameworks */, + 3380327229784D96002D32AE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3380327929784D96002D32AE /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 3380327429784D96002D32AE /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 45F307B61DA47FC553C87CA6 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 246FA3B3BBF06301555F5A51 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -197,46 +208,28 @@ productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; - F76AC1DB26671E960040C8BC /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = F76AC1E526671E960040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 31566AD39C1C7EF9EB261E6F /* [CP] Check Pods Manifest.lock */, - F76AC1D826671E960040C8BC /* Sources */, - F76AC1D926671E960040C8BC /* Frameworks */, - F76AC1DA26671E960040C8BC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - F76AC1E226671E960040C8BC /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = F76AC1DC26671E960040C8BC /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1400; LastUpgradeCheck = 1300; - ORGANIZATIONNAME = "The Flutter Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { + 3380327329784D96002D32AE = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - }; - F76AC1DB26671E960040C8BC = { - CreatedOnToolsVersion = 12.5; - ProvisioningStyle = Automatic; - TestTargetID = 97C146ED1CF9000F007C117D; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -249,53 +242,48 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, - F76AC1DB26671E960040C8BC /* RunnerTests */, + 3380327329784D96002D32AE /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { + 3380327229784D96002D32AE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - F76AC1DA26671E960040C8BC /* Resources */ = { + 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 31566AD39C1C7EF9EB261E6F /* [CP] Check Pods Manifest.lock */ = { + 246FA3B3BBF06301555F5A51 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { @@ -312,66 +300,91 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 45F307B61DA47FC553C87CA6 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Run Script"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + 9144B1C9B36C0B00C1DF8FBB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { + 3380327029784D96002D32AE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, - 2D9222481EC32A19007564B0 /* GeneratedPluginRegistrant.m in Sources */, + 33258D7929818305006BAA98 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - F76AC1D826671E960040C8BC /* Sources */ = { + 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F76AC1DF26671E960040C8BC /* PathProviderTests.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - F76AC1E226671E960040C8BC /* PBXTargetDependency */ = { + 3380327929784D96002D32AE /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = F76AC1E126671E960040C8BC /* PBXContainerItemProxy */; + targetProxy = 3380327829784D96002D32AE /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -395,11 +408,126 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.pathProviderFoundationExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 3380327A29784D96002D32AE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1E28C831B7D8EA9408BFB69A /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 3380327B29784D96002D32AE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C1E50EBAA845915BAF5591C9 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 3380327C29784D96002D32AE /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 86F7986E9DC17432CC8AE464 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -443,7 +571,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -455,7 +583,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -493,9 +620,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -506,20 +636,20 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pathProviderFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -528,77 +658,51 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - F76AC1E326671E960040C8BC /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6EB685EA3DDA2EED39600D11 /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; - }; - name = Debug; - }; - F76AC1E426671E960040C8BC /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 29F2567B3AE74A9113ED3394 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pathProviderFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + 3380327D29784D96002D32AE /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, + 3380327A29784D96002D32AE /* Debug */, + 3380327B29784D96002D32AE /* Release */, + 3380327C29784D96002D32AE /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F76AC1E526671E960040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - F76AC1E326671E960040C8BC /* Debug */, - F76AC1E426671E960040C8BC /* Release */, + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/path_provider/path_provider_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 95% rename from packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ec3713b95db5..b5d62ddeb711 100644 --- a/packages/path_provider/path_provider_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -38,10 +38,11 @@ + skipped = "NO" + parallelizable = "YES"> @@ -71,7 +72,7 @@ - + - + @@ -10,13 +10,20 @@ - - + + - - + + + + + + + + + @@ -24,4 +31,7 @@ + + + diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner/Base.lproj/Main.storyboard b/packages/path_provider/path_provider_foundation/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/path_provider/path_provider_ios/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/path_provider/path_provider_foundation/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner/Info.plist b/packages/path_provider/path_provider_foundation/example/ios/Runner/Info.plist similarity index 78% rename from packages/path_provider/path_provider_ios/example/ios/Runner/Info.plist rename to packages/path_provider/path_provider_foundation/example/ios/Runner/Info.plist index 342db6a5dcaf..5bdb9bcc0635 100644 --- a/packages/path_provider/path_provider_ios/example/ios/Runner/Info.plist +++ b/packages/path_provider/path_provider_foundation/example/ios/Runner/Info.plist @@ -3,7 +3,9 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Path Provider Foundation CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,25 +13,21 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - path_provider_example + path_provider_foundation_example CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - 1 + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main - UIRequiredDeviceCapabilities - - arm64 - UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -45,5 +43,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Runner-Bridging-Header.h b/packages/path_provider/path_provider_foundation/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Runner-Bridging-Header.h rename to packages/path_provider/path_provider_foundation/example/ios/Runner/Runner-Bridging-Header.h diff --git a/packages/path_provider/path_provider_macos/example/lib/main.dart b/packages/path_provider/path_provider_foundation/example/lib/main.dart similarity index 88% rename from packages/path_provider/path_provider_macos/example/lib/main.dart rename to packages/path_provider/path_provider_foundation/example/lib/main.dart index 13a6fada5fef..cc3fc13de89f 100644 --- a/packages/path_provider/path_provider_macos/example/lib/main.dart +++ b/packages/path_provider/path_provider_foundation/example/lib/main.dart @@ -22,6 +22,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { String? _tempDirectory = 'Unknown'; String? _downloadsDirectory = 'Unknown'; + String? _libraryDirectory = 'Unknown'; String? _appSupportDirectory = 'Unknown'; String? _documentsDirectory = 'Unknown'; @@ -36,6 +37,7 @@ class _MyAppState extends State { String? tempDirectory; String? downloadsDirectory; String? appSupportDirectory; + String? libraryDirectory; String? documentsDirectory; final PathProviderPlatform provider = PathProviderPlatform.instance; @@ -56,6 +58,12 @@ class _MyAppState extends State { documentsDirectory = 'Failed to get documents directory: $exception'; } + try { + libraryDirectory = await provider.getLibraryPath(); + } catch (exception) { + libraryDirectory = 'Failed to get library directory: $exception'; + } + try { appSupportDirectory = await provider.getApplicationSupportPath(); } catch (exception) { @@ -65,6 +73,7 @@ class _MyAppState extends State { setState(() { _tempDirectory = tempDirectory; _downloadsDirectory = downloadsDirectory; + _libraryDirectory = libraryDirectory; _appSupportDirectory = appSupportDirectory; _documentsDirectory = documentsDirectory; }); @@ -83,6 +92,7 @@ class _MyAppState extends State { Text('Temp Directory: $_tempDirectory\n'), Text('Documents Directory: $_documentsDirectory\n'), Text('Downloads Directory: $_downloadsDirectory\n'), + Text('Library Directory: $_libraryDirectory\n'), Text('Application Support Directory: $_appSupportDirectory\n'), ], ), diff --git a/packages/path_provider/path_provider_macos/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/path_provider/path_provider_foundation/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/path_provider/path_provider_foundation/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/packages/path_provider/path_provider_macos/example/macos/Flutter/Flutter-Release.xcconfig b/packages/path_provider/path_provider_foundation/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/path_provider/path_provider_foundation/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/packages/path_provider/path_provider_macos/example/macos/Podfile b/packages/path_provider/path_provider_foundation/example/macos/Podfile similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Podfile rename to packages/path_provider/path_provider_foundation/example/macos/Podfile diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner.xcodeproj/project.pbxproj b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj similarity index 99% rename from packages/path_provider/path_provider_macos/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj index a63463993c6e..5abc18a86297 100644 --- a/packages/path_provider/path_provider_macos/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj @@ -82,7 +82,7 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 33EBD3A726728EA70013E557 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 33EBD3A926728EA70013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 33EBD3A926728EA70013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../../../darwin/RunnerTests/RunnerTests.swift; sourceTree = ""; }; 33EBD3AB26728EA70013E557 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46139048DB9F59D473B61B5E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; @@ -367,11 +367,11 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/path_provider_macos/path_provider_macos.framework", + "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/path_provider/path_provider_foundation/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/path_provider/path_provider_foundation/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/AppDelegate.swift b/packages/path_provider/path_provider_foundation/example/macos/Runner/AppDelegate.swift similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/AppDelegate.swift rename to packages/path_provider/path_provider_foundation/example/macos/Runner/AppDelegate.swift diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/path_provider/path_provider_foundation/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Configs/Debug.xcconfig b/packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Configs/Debug.xcconfig rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Debug.xcconfig diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Configs/Release.xcconfig b/packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Configs/Release.xcconfig rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Release.xcconfig diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Configs/Warnings.xcconfig b/packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/DebugProfile.entitlements b/packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/DebugProfile.entitlements rename to packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Info.plist b/packages/path_provider/path_provider_foundation/example/macos/Runner/Info.plist similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Info.plist rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Info.plist diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/MainFlutterWindow.swift b/packages/path_provider/path_provider_foundation/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/MainFlutterWindow.swift rename to packages/path_provider/path_provider_foundation/example/macos/Runner/MainFlutterWindow.swift diff --git a/packages/path_provider/path_provider_macos/example/macos/Runner/Release.entitlements b/packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/Runner/Release.entitlements rename to packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements diff --git a/packages/path_provider/path_provider_ios/example/ios/RunnerTests/Info.plist b/packages/path_provider/path_provider_foundation/example/macos/RunnerTests/Info.plist similarity index 100% rename from packages/path_provider/path_provider_ios/example/ios/RunnerTests/Info.plist rename to packages/path_provider/path_provider_foundation/example/macos/RunnerTests/Info.plist diff --git a/packages/path_provider/path_provider_macos/example/pubspec.yaml b/packages/path_provider/path_provider_foundation/example/pubspec.yaml similarity index 88% rename from packages/path_provider/path_provider_macos/example/pubspec.yaml rename to packages/path_provider/path_provider_foundation/example/pubspec.yaml index 8c69e69ce122..fcf599564659 100644 --- a/packages/path_provider/path_provider_macos/example/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/example/pubspec.yaml @@ -4,14 +4,14 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: sdk: flutter - path_provider_macos: + path_provider_foundation: # When depending on this package from a real application you should use: - # path_provider_macos: ^x.y.z + # path_provider_foundation: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. diff --git a/packages/path_provider/path_provider_ios/example/test_driver/integration_test.dart b/packages/path_provider/path_provider_foundation/example/test_driver/integration_test.dart similarity index 100% rename from packages/path_provider/path_provider_ios/example/test_driver/integration_test.dart rename to packages/path_provider/path_provider_foundation/example/test_driver/integration_test.dart diff --git a/packages/path_provider/path_provider_foundation/ios/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_foundation/ios/Classes/PathProviderPlugin.swift new file mode 120000 index 000000000000..47ec1bfb28ca --- /dev/null +++ b/packages/path_provider/path_provider_foundation/ios/Classes/PathProviderPlugin.swift @@ -0,0 +1 @@ +../../darwin/Classes/PathProviderPlugin.swift \ No newline at end of file diff --git a/packages/path_provider/path_provider_foundation/ios/Classes/messages.g.swift b/packages/path_provider/path_provider_foundation/ios/Classes/messages.g.swift new file mode 120000 index 000000000000..11bcf06e96a8 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/ios/Classes/messages.g.swift @@ -0,0 +1 @@ +../../darwin/Classes/messages.g.swift \ No newline at end of file diff --git a/packages/path_provider/path_provider_foundation/ios/README.md b/packages/path_provider/path_provider_foundation/ios/README.md new file mode 100644 index 000000000000..fd7261950f35 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/ios/README.md @@ -0,0 +1,4 @@ +This only contains symlinks to ../darwin, to support versions of Flutter +prior that don't include https://github.com/flutter/flutter/pull/115337. +Once the minimum Flutter version supported by this implementation is one that +includes that functionality, this directory should be removed. diff --git a/packages/path_provider/path_provider_foundation/ios/path_provider_foundation.podspec b/packages/path_provider/path_provider_foundation/ios/path_provider_foundation.podspec new file mode 120000 index 000000000000..feae183dd621 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/ios/path_provider_foundation.podspec @@ -0,0 +1 @@ +../darwin/path_provider_foundation.podspec \ No newline at end of file diff --git a/packages/path_provider/path_provider_foundation/lib/messages.g.dart b/packages/path_provider/path_provider_foundation/lib/messages.g.dart new file mode 100644 index 000000000000..81a9cd5cc525 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/lib/messages.g.dart @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +enum DirectoryType { + applicationDocuments, + applicationSupport, + downloads, + library, + temp, +} + +class PathProviderApi { + /// Constructor for [PathProviderApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + PathProviderApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future getDirectoryPath(DirectoryType arg_type) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PathProviderApi.getDirectoryPath', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_type.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as String?); + } + } +} diff --git a/packages/path_provider/path_provider_ios/lib/path_provider_ios.dart b/packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart similarity index 51% rename from packages/path_provider/path_provider_ios/lib/path_provider_ios.dart rename to packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart index 05be0534764a..9fdda6935245 100644 --- a/packages/path_provider/path_provider_ios/lib/path_provider_ios.dart +++ b/packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart @@ -5,26 +5,27 @@ import 'dart:io'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + import 'messages.g.dart'; -/// The iOS implementation of [PathProviderPlatform]. -class PathProviderIOS extends PathProviderPlatform { - /// The method channel used to interact with the native platform. +/// The iOS and macOS implementation of [PathProviderPlatform]. +class PathProviderFoundation extends PathProviderPlatform { final PathProviderApi _pathProvider = PathProviderApi(); /// Registers this class as the default instance of [PathProviderPlatform] static void registerWith() { - PathProviderPlatform.instance = PathProviderIOS(); + PathProviderPlatform.instance = PathProviderFoundation(); } @override - Future getTemporaryPath() async { - return _pathProvider.getTemporaryPath(); + Future getTemporaryPath() { + return _pathProvider.getDirectoryPath(DirectoryType.temp); } @override Future getApplicationSupportPath() async { - final String? path = await _pathProvider.getApplicationSupportPath(); + final String? path = + await _pathProvider.getDirectoryPath(DirectoryType.applicationSupport); if (path != null) { // Ensure the directory exists before returning it, for consistency with // other platforms. @@ -34,34 +35,37 @@ class PathProviderIOS extends PathProviderPlatform { } @override - Future getLibraryPath() async { - return _pathProvider.getLibraryPath(); + Future getLibraryPath() { + return _pathProvider.getDirectoryPath(DirectoryType.library); } @override - Future getApplicationDocumentsPath() async { - return _pathProvider.getApplicationDocumentsPath(); + Future getApplicationDocumentsPath() { + return _pathProvider.getDirectoryPath(DirectoryType.applicationDocuments); } @override Future getExternalStoragePath() async { - throw UnsupportedError('getExternalStoragePath is not supported on iOS'); + throw UnsupportedError( + 'getExternalStoragePath is not supported on this platform'); } @override Future?> getExternalCachePaths() async { - throw UnsupportedError('getExternalCachePaths is not supported on iOS'); + throw UnsupportedError( + 'getExternalCachePaths is not supported on this platform'); } @override Future?> getExternalStoragePaths({ StorageDirectory? type, }) async { - throw UnsupportedError('getExternalStoragePaths is not supported on iOS'); + throw UnsupportedError( + 'getExternalStoragePaths is not supported on this platform'); } @override - Future getDownloadsPath() async { - throw UnsupportedError('getDownloadsPath is not supported on iOS'); + Future getDownloadsPath() { + return _pathProvider.getDirectoryPath(DirectoryType.downloads); } } diff --git a/packages/path_provider/path_provider_foundation/macos/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_foundation/macos/Classes/PathProviderPlugin.swift new file mode 120000 index 000000000000..47ec1bfb28ca --- /dev/null +++ b/packages/path_provider/path_provider_foundation/macos/Classes/PathProviderPlugin.swift @@ -0,0 +1 @@ +../../darwin/Classes/PathProviderPlugin.swift \ No newline at end of file diff --git a/packages/path_provider/path_provider_foundation/macos/Classes/messages.g.swift b/packages/path_provider/path_provider_foundation/macos/Classes/messages.g.swift new file mode 120000 index 000000000000..11bcf06e96a8 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/macos/Classes/messages.g.swift @@ -0,0 +1 @@ +../../darwin/Classes/messages.g.swift \ No newline at end of file diff --git a/packages/path_provider/path_provider_foundation/macos/README.md b/packages/path_provider/path_provider_foundation/macos/README.md new file mode 100644 index 000000000000..fd7261950f35 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/macos/README.md @@ -0,0 +1,4 @@ +This only contains symlinks to ../darwin, to support versions of Flutter +prior that don't include https://github.com/flutter/flutter/pull/115337. +Once the minimum Flutter version supported by this implementation is one that +includes that functionality, this directory should be removed. diff --git a/packages/path_provider/path_provider_foundation/macos/path_provider_foundation.podspec b/packages/path_provider/path_provider_foundation/macos/path_provider_foundation.podspec new file mode 120000 index 000000000000..feae183dd621 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/macos/path_provider_foundation.podspec @@ -0,0 +1 @@ +../darwin/path_provider_foundation.podspec \ No newline at end of file diff --git a/packages/path_provider/path_provider_foundation/pigeons/copyright.txt b/packages/path_provider/path_provider_foundation/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/path_provider/path_provider_foundation/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/path_provider/path_provider_ios/pigeons/messages.dart b/packages/path_provider/path_provider_foundation/pigeons/messages.dart similarity index 63% rename from packages/path_provider/path_provider_ios/pigeons/messages.dart rename to packages/path_provider/path_provider_foundation/pigeons/messages.dart index 2ed79914e821..8c82ab4fcf14 100644 --- a/packages/path_provider/path_provider_ios/pigeons/messages.dart +++ b/packages/path_provider/path_provider_foundation/pigeons/messages.dart @@ -5,17 +5,20 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( input: 'pigeons/messages.dart', - objcOptions: ObjcOptions(prefix: 'FLT'), - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + swiftOut: 'macos/Classes/messages.g.swift', dartOut: 'lib/messages.g.dart', dartTestOut: 'test/messages_test.g.dart', copyrightHeader: 'pigeons/copyright.txt', )) +enum DirectoryType { + applicationDocuments, + applicationSupport, + downloads, + library, + temp, +} + @HostApi(dartHostTestHandler: 'TestPathProviderApi') abstract class PathProviderApi { - String? getTemporaryPath(); - String? getApplicationSupportPath(); - String? getLibraryPath(); - String? getApplicationDocumentsPath(); + String? getDirectoryPath(DirectoryType type); } diff --git a/packages/path_provider/path_provider_macos/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml similarity index 52% rename from packages/path_provider/path_provider_macos/pubspec.yaml rename to packages/path_provider/path_provider_foundation/pubspec.yaml index 289e13103ddd..30dd655acc00 100644 --- a/packages/path_provider/path_provider_macos/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/pubspec.yaml @@ -1,20 +1,25 @@ -name: path_provider_macos -description: macOS implementation of the path_provider plugin -repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_macos +name: path_provider_foundation +description: iOS and macOS implementation of the path_provider plugin +repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.0.6 +version: 2.1.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: implements: path_provider platforms: + ios: + pluginClass: PathProviderPlugin + dartPluginClass: PathProviderFoundation + sharedDarwinSource: true macos: pluginClass: PathProviderPlugin - dartPluginClass: PathProviderMacOS + dartPluginClass: PathProviderFoundation + sharedDarwinSource: true dependencies: flutter: @@ -22,6 +27,9 @@ dependencies: path_provider_platform_interface: ^2.0.1 dev_dependencies: + build_runner: ^2.3.2 flutter_test: sdk: flutter + mockito: ^5.3.2 path: ^1.8.0 + pigeon: ^5.0.0 diff --git a/packages/path_provider/path_provider_foundation/test/messages_test.g.dart b/packages/path_provider/path_provider_foundation/test/messages_test.g.dart new file mode 100644 index 000000000000..9fb9b954cede --- /dev/null +++ b/packages/path_provider/path_provider_foundation/test/messages_test.g.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import +// ignore_for_file: avoid_relative_lib_imports +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:path_provider_foundation/messages.g.dart'; + +abstract class TestPathProviderApi { + static const MessageCodec codec = StandardMessageCodec(); + + String? getDirectoryPath(DirectoryType type); + + static void setup(TestPathProviderApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PathProviderApi.getDirectoryPath', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.PathProviderApi.getDirectoryPath was null.'); + final List args = (message as List?)!; + final DirectoryType? arg_type = + args[0] == null ? null : DirectoryType.values[args[0] as int]; + assert(arg_type != null, + 'Argument for dev.flutter.pigeon.PathProviderApi.getDirectoryPath was null, expected non-null DirectoryType.'); + final String? output = api.getDirectoryPath(arg_type!); + return [output]; + }); + } + } + } +} diff --git a/packages/path_provider/path_provider_macos/test/path_provider_macos_test.dart b/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart similarity index 50% rename from packages/path_provider/path_provider_macos/test/path_provider_macos_test.dart rename to packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart index 7e783aad24e9..e291e3bf25d8 100644 --- a/packages/path_provider/path_provider_macos/test/path_provider_macos_test.dart +++ b/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart @@ -4,61 +4,33 @@ import 'dart:io'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'package:path/path.dart' as p; -import 'package:path_provider_macos/path_provider_macos.dart'; +import 'package:path_provider_foundation/messages.g.dart'; +import 'package:path_provider_foundation/path_provider_foundation.dart'; +import 'messages_test.g.dart'; +import 'path_provider_foundation_test.mocks.dart'; + +@GenerateMocks([TestPathProviderApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('PathProviderMacOS', () { - late PathProviderMacOS pathProvider; - late List log; + group('PathProviderFoundation', () { + late PathProviderFoundation pathProvider; + late MockTestPathProviderApi mockApi; // These unit tests use the actual filesystem, since an injectable // filesystem would add a runtime dependency to the package, so everything // is contained to a temporary directory. late Directory testRoot; - late String temporaryPath; - late String applicationSupportPath; - late String libraryPath; - late String applicationDocumentsPath; - late String downloadsPath; - setUp(() async { - pathProvider = PathProviderMacOS(); - testRoot = Directory.systemTemp.createTempSync(); - final String basePath = testRoot.path; - temporaryPath = p.join(basePath, 'temporary', 'path'); - applicationSupportPath = - p.join(basePath, 'application', 'support', 'path'); - libraryPath = p.join(basePath, 'library', 'path'); - applicationDocumentsPath = - p.join(basePath, 'application', 'documents', 'path'); - downloadsPath = p.join(basePath, 'downloads', 'path'); - - log = []; - TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger - .setMockMethodCallHandler(pathProvider.methodChannel, - (MethodCall methodCall) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getTemporaryDirectory': - return temporaryPath; - case 'getApplicationSupportDirectory': - return applicationSupportPath; - case 'getLibraryDirectory': - return libraryPath; - case 'getApplicationDocumentsDirectory': - return applicationDocumentsPath; - case 'getDownloadsDirectory': - return downloadsPath; - default: - return null; - } - }); + pathProvider = PathProviderFoundation(); + mockApi = MockTestPathProviderApi(); + TestPathProviderApi.setup(mockApi); }); tearDown(() { @@ -66,57 +38,71 @@ void main() { }); test('getTemporaryPath', () async { + final String temporaryPath = p.join(testRoot.path, 'temporary', 'path'); + when(mockApi.getDirectoryPath(DirectoryType.temp)) + .thenReturn(temporaryPath); + final String? path = await pathProvider.getTemporaryPath(); - expect( - log, - [isMethodCall('getTemporaryDirectory', arguments: null)], - ); + + verify(mockApi.getDirectoryPath(DirectoryType.temp)); expect(path, temporaryPath); }); test('getApplicationSupportPath', () async { + final String applicationSupportPath = + p.join(testRoot.path, 'application', 'support', 'path'); + when(mockApi.getDirectoryPath(DirectoryType.applicationSupport)) + .thenReturn(applicationSupportPath); + final String? path = await pathProvider.getApplicationSupportPath(); - expect( - log, - [ - isMethodCall('getApplicationSupportDirectory', arguments: null) - ], - ); + + verify(mockApi.getDirectoryPath(DirectoryType.applicationSupport)); expect(path, applicationSupportPath); }); test('getApplicationSupportPath creates the directory if necessary', () async { + final String applicationSupportPath = + p.join(testRoot.path, 'application', 'support', 'path'); + when(mockApi.getDirectoryPath(DirectoryType.applicationSupport)) + .thenReturn(applicationSupportPath); + final String? path = await pathProvider.getApplicationSupportPath(); + expect(Directory(path!).existsSync(), isTrue); }); test('getLibraryPath', () async { + final String libraryPath = p.join(testRoot.path, 'library', 'path'); + when(mockApi.getDirectoryPath(DirectoryType.library)) + .thenReturn(libraryPath); + final String? path = await pathProvider.getLibraryPath(); - expect( - log, - [isMethodCall('getLibraryDirectory', arguments: null)], - ); + + verify(mockApi.getDirectoryPath(DirectoryType.library)); expect(path, libraryPath); }); test('getApplicationDocumentsPath', () async { + final String applicationDocumentsPath = + p.join(testRoot.path, 'application', 'documents', 'path'); + when(mockApi.getDirectoryPath(DirectoryType.applicationDocuments)) + .thenReturn(applicationDocumentsPath); + final String? path = await pathProvider.getApplicationDocumentsPath(); - expect( - log, - [ - isMethodCall('getApplicationDocumentsDirectory', arguments: null) - ], - ); + + verify(mockApi.getDirectoryPath(DirectoryType.applicationDocuments)); expect(path, applicationDocumentsPath); }); test('getDownloadsPath', () async { + final String downloadsPath = p.join(testRoot.path, 'downloads', 'path'); + when(mockApi.getDirectoryPath(DirectoryType.downloads)) + .thenReturn(downloadsPath); + final String? result = await pathProvider.getDownloadsPath(); - expect( - log, - [isMethodCall('getDownloadsDirectory', arguments: null)], - ); + + verify(mockApi.getDirectoryPath(DirectoryType.downloads)); expect(result, downloadsPath); }); diff --git a/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.mocks.dart b/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.mocks.dart new file mode 100644 index 000000000000..cd3a1c7e8416 --- /dev/null +++ b/packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.mocks.dart @@ -0,0 +1,37 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in path_provider_foundation/test/path_provider_foundation_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:mockito/mockito.dart' as _i1; +import 'package:path_provider_foundation/messages.g.dart' as _i3; + +import 'messages_test.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestPathProviderApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestPathProviderApi extends _i1.Mock + implements _i2.TestPathProviderApi { + MockTestPathProviderApi() { + _i1.throwOnMissingStub(this); + } + + @override + String? getDirectoryPath(_i3.DirectoryType? type) => + (super.noSuchMethod(Invocation.method( + #getDirectoryPath, + [type], + )) as String?); +} diff --git a/packages/path_provider/path_provider_ios/CHANGELOG.md b/packages/path_provider/path_provider_ios/CHANGELOG.md deleted file mode 100644 index ffc158c19156..000000000000 --- a/packages/path_provider/path_provider_ios/CHANGELOG.md +++ /dev/null @@ -1,28 +0,0 @@ -## NEXT - -* Updates minimum Flutter version to 2.10. - -## 2.0.11 - -* Lower minimim version back to 2.8. - -## 2.0.10 - -* Switches backend to pigeon. - -## 2.0.9 - -* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors - lint warnings. - -## 2.0.8 - -* Switches to a package-internal implementation of the platform interface. - -## 2.0.7 - -* Fixes link in README. - -## 2.0.6 - -* Split from `path_provider` as a federated implementation. diff --git a/packages/path_provider/path_provider_ios/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider_ios/example/integration_test/path_provider_test.dart deleted file mode 100644 index 5c6cf5a63579..000000000000 --- a/packages/path_provider/path_provider_ios/example/integration_test/path_provider_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - testWidgets('getTemporaryDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getTemporaryPath(); - _verifySampleFile(result, 'temporaryDirectory'); - }); - - testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getApplicationDocumentsPath(); - _verifySampleFile(result, 'applicationDocuments'); - }); - - testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getApplicationSupportPath(); - _verifySampleFile(result, 'applicationSupport'); - }); - - testWidgets('getLibraryDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - final String? result = await provider.getLibraryPath(); - _verifySampleFile(result, 'library'); - }); - - testWidgets('getExternalStorageDirectory', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - expect(() => provider.getExternalStoragePath(), - throwsA(isInstanceOf())); - }); - - testWidgets('getExternalCacheDirectories', (WidgetTester tester) async { - final PathProviderPlatform provider = PathProviderPlatform.instance; - expect(() => provider.getExternalCachePaths(), - throwsA(isInstanceOf())); - }); -} - -/// Verify a file called [name] in [directoryPath] by recreating it with test -/// contents when necessary. -void _verifySampleFile(String? directoryPath, String name) { - expect(directoryPath, isNotNull); - if (directoryPath == null) { - return; - } - final Directory directory = Directory(directoryPath); - final File file = File('${directory.path}${Platform.pathSeparator}$name'); - - if (file.existsSync()) { - file.deleteSync(); - expect(file.existsSync(), isFalse); - } - - file.writeAsStringSync('Hello world!'); - expect(file.readAsStringSync(), 'Hello world!'); - expect(directory.listSync(), isNotEmpty); - file.deleteSync(); -} diff --git a/packages/path_provider/path_provider_ios/example/ios/Flutter/Debug.xcconfig b/packages/path_provider/path_provider_ios/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 9803018ca79d..000000000000 --- a/packages/path_provider/path_provider_ios/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "Generated.xcconfig" -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" diff --git a/packages/path_provider/path_provider_ios/example/ios/Flutter/Release.xcconfig b/packages/path_provider/path_provider_ios/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index a4a8c604e13d..000000000000 --- a/packages/path_provider/path_provider_ios/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "Generated.xcconfig" -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.h b/packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.h deleted file mode 100644 index 0681d288bb70..000000000000 --- a/packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.m b/packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.m deleted file mode 100644 index b790a0a52635..000000000000 --- a/packages/path_provider/path_provider_ios/example/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -@end diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner/main.m b/packages/path_provider/path_provider_ios/example/ios/Runner/main.m deleted file mode 100644 index f143297b30d6..000000000000 --- a/packages/path_provider/path_provider_ios/example/ios/Runner/main.m +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import -#import "AppDelegate.h" - -int main(int argc, char *argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/packages/path_provider/path_provider_ios/example/ios/RunnerTests/PathProviderTests.m b/packages/path_provider/path_provider_ios/example/ios/RunnerTests/PathProviderTests.m deleted file mode 100644 index 87d227795614..000000000000 --- a/packages/path_provider/path_provider_ios/example/ios/RunnerTests/PathProviderTests.m +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import path_provider_ios; -@import XCTest; - -@interface PathProviderTests : XCTestCase -@end - -@implementation PathProviderTests - -- (void)testPlugin { - FLTPathProviderPlugin *plugin = [[FLTPathProviderPlugin alloc] init]; - XCTAssertNotNil(plugin); -} - -@end diff --git a/packages/path_provider/path_provider_ios/example/lib/main.dart b/packages/path_provider/path_provider_ios/example/lib/main.dart deleted file mode 100644 index d7140b76a06b..000000000000 --- a/packages/path_provider/path_provider_ios/example/lib/main.dart +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: public_member_api_docs - -import 'package:flutter/material.dart'; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Path Provider', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Path Provider'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - final PathProviderPlatform provider = PathProviderPlatform.instance; - Future? _tempDirectory; - Future? _appSupportDirectory; - Future? _appLibraryDirectory; - Future? _appDocumentsDirectory; - - void _requestTempDirectory() { - setState(() { - _tempDirectory = provider.getTemporaryPath(); - }); - } - - Widget _buildDirectory( - BuildContext context, AsyncSnapshot snapshot) { - Text text = const Text(''); - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - text = Text('Error: ${snapshot.error}'); - } else if (snapshot.hasData) { - text = Text('path: ${snapshot.data}'); - } else { - text = const Text('path unavailable'); - } - } - return Padding(padding: const EdgeInsets.all(16.0), child: text); - } - - void _requestAppDocumentsDirectory() { - setState(() { - _appDocumentsDirectory = provider.getApplicationDocumentsPath(); - }); - } - - void _requestAppSupportDirectory() { - setState(() { - _appSupportDirectory = provider.getApplicationSupportPath(); - }); - } - - void _requestAppLibraryDirectory() { - setState(() { - _appLibraryDirectory = provider.getLibraryPath(); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Center( - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: _requestTempDirectory, - child: const Text('Get Temporary Directory'), - ), - ), - FutureBuilder( - future: _tempDirectory, builder: _buildDirectory), - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: _requestAppDocumentsDirectory, - child: const Text('Get Application Documents Directory'), - ), - ), - FutureBuilder( - future: _appDocumentsDirectory, builder: _buildDirectory), - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: _requestAppSupportDirectory, - child: const Text('Get Application Support Directory'), - ), - ), - FutureBuilder( - future: _appSupportDirectory, builder: _buildDirectory), - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: _requestAppLibraryDirectory, - child: const Text('Get Application Library Directory'), - ), - ), - FutureBuilder( - future: _appLibraryDirectory, builder: _buildDirectory), - ], - ), - ), - ); - } -} diff --git a/packages/path_provider/path_provider_ios/example/pubspec.yaml b/packages/path_provider/path_provider_ios/example/pubspec.yaml deleted file mode 100644 index f1d885513948..000000000000 --- a/packages/path_provider/path_provider_ios/example/pubspec.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: path_provider_example -description: Demonstrates how to use the path_provider plugin. -publish_to: none - -environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" - -dependencies: - flutter: - sdk: flutter - path_provider_ios: - # When depending on this package from a real application you should use: - # path_provider_ios: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - path_provider_platform_interface: ^2.0.0 - -dev_dependencies: - flutter_driver: - sdk: flutter - flutter_test: - sdk: flutter - integration_test: - sdk: flutter - -flutter: - uses-material-design: true diff --git a/packages/path_provider/path_provider_ios/ios/Assets/.gitkeep b/packages/path_provider/path_provider_ios/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m b/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m deleted file mode 100644 index 82b8df5382fb..000000000000 --- a/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FLTPathProviderPlugin.h" -#import "messages.g.h" - -static NSString *GetDirectoryOfType(NSSearchPathDirectory dir) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES); - return paths.firstObject; -} - -@interface FLTPathProviderPlugin () -@end - -@implementation FLTPathProviderPlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FLTPathProviderPlugin *plugin = [[FLTPathProviderPlugin alloc] init]; - FLTPathProviderApiSetup(registrar.messenger, plugin); -} - -- (nullable NSString *)getApplicationDocumentsPathWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return GetDirectoryOfType(NSDocumentDirectory); -} - -- (nullable NSString *)getApplicationSupportPathWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return GetDirectoryOfType(NSApplicationSupportDirectory); -} - -- (nullable NSString *)getLibraryPathWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return GetDirectoryOfType(NSLibraryDirectory); -} - -- (nullable NSString *)getTemporaryPathWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return GetDirectoryOfType(NSCachesDirectory); -} - -@end diff --git a/packages/path_provider/path_provider_ios/ios/Classes/messages.g.h b/packages/path_provider/path_provider_ios/ios/Classes/messages.g.h deleted file mode 100644 index b6c1d4d92dd4..000000000000 --- a/packages/path_provider/path_provider_ios/ios/Classes/messages.g.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v3.1.5), do not edit directly. -// See also: https://pub.dev/packages/pigeon -#import -@protocol FlutterBinaryMessenger; -@protocol FlutterMessageCodec; -@class FlutterError; -@class FlutterStandardTypedData; - -NS_ASSUME_NONNULL_BEGIN - -/// The codec used by FLTPathProviderApi. -NSObject *FLTPathProviderApiGetCodec(void); - -@protocol FLTPathProviderApi -- (nullable NSString *)getTemporaryPathWithError:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)getApplicationSupportPathWithError:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)getLibraryPathWithError:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)getApplicationDocumentsPathWithError: - (FlutterError *_Nullable *_Nonnull)error; -@end - -extern void FLTPathProviderApiSetup(id binaryMessenger, - NSObject *_Nullable api); - -NS_ASSUME_NONNULL_END diff --git a/packages/path_provider/path_provider_ios/ios/Classes/messages.g.m b/packages/path_provider/path_provider_ios/ios/Classes/messages.g.m deleted file mode 100644 index 2589df1837e7..000000000000 --- a/packages/path_provider/path_provider_ios/ios/Classes/messages.g.m +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v3.1.5), do not edit directly. -// See also: https://pub.dev/packages/pigeon -#import "messages.g.h" -#import - -#if !__has_feature(objc_arc) -#error File requires ARC to be enabled. -#endif - -static NSDictionary *wrapResult(id result, FlutterError *error) { - NSDictionary *errorDict = (NSDictionary *)[NSNull null]; - if (error) { - errorDict = @{ - @"code" : (error.code ?: [NSNull null]), - @"message" : (error.message ?: [NSNull null]), - @"details" : (error.details ?: [NSNull null]), - }; - } - return @{ - @"result" : (result ?: [NSNull null]), - @"error" : errorDict, - }; -} - -@interface FLTPathProviderApiCodecReader : FlutterStandardReader -@end -@implementation FLTPathProviderApiCodecReader -@end - -@interface FLTPathProviderApiCodecWriter : FlutterStandardWriter -@end -@implementation FLTPathProviderApiCodecWriter -@end - -@interface FLTPathProviderApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation FLTPathProviderApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[FLTPathProviderApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[FLTPathProviderApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *FLTPathProviderApiGetCodec() { - static dispatch_once_t sPred = 0; - static FlutterStandardMessageCodec *sSharedObject = nil; - dispatch_once(&sPred, ^{ - FLTPathProviderApiCodecReaderWriter *readerWriter = - [[FLTPathProviderApiCodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - -void FLTPathProviderApiSetup(id binaryMessenger, - NSObject *api) { - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.PathProviderApi.getTemporaryPath" - binaryMessenger:binaryMessenger - codec:FLTPathProviderApiGetCodec()]; - if (api) { - NSCAssert( - [api respondsToSelector:@selector(getTemporaryPathWithError:)], - @"FLTPathProviderApi api (%@) doesn't respond to @selector(getTemporaryPathWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSString *output = [api getTemporaryPathWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath" - binaryMessenger:binaryMessenger - codec:FLTPathProviderApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(getApplicationSupportPathWithError:)], - @"FLTPathProviderApi api (%@) doesn't respond to " - @"@selector(getApplicationSupportPathWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSString *output = [api getApplicationSupportPathWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.PathProviderApi.getLibraryPath" - binaryMessenger:binaryMessenger - codec:FLTPathProviderApiGetCodec()]; - if (api) { - NSCAssert( - [api respondsToSelector:@selector(getLibraryPathWithError:)], - @"FLTPathProviderApi api (%@) doesn't respond to @selector(getLibraryPathWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSString *output = [api getLibraryPathWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath" - binaryMessenger:binaryMessenger - codec:FLTPathProviderApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(getApplicationDocumentsPathWithError:)], - @"FLTPathProviderApi api (%@) doesn't respond to " - @"@selector(getApplicationDocumentsPathWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSString *output = [api getApplicationDocumentsPathWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } -} diff --git a/packages/path_provider/path_provider_ios/ios/path_provider_ios.podspec b/packages/path_provider/path_provider_ios/ios/path_provider_ios.podspec deleted file mode 100644 index f1f94e996093..000000000000 --- a/packages/path_provider/path_provider_ios/ios/path_provider_ios.podspec +++ /dev/null @@ -1,22 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# -Pod::Spec.new do |s| - s.name = 'path_provider_ios' - s.version = '0.0.1' - s.summary = 'Flutter Path Provider' - s.description = <<-DESC -A Flutter plugin for getting commonly used locations on the filesystem. -Downloaded by pub (not CocoaPods). - DESC - s.homepage = 'https://github.com/flutter/plugins' - s.license = { :type => 'BSD', :file => '../LICENSE' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_ios' } - s.documentation_url = 'https://pub.dev/packages/path_provider' - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' - s.dependency 'Flutter' - s.platform = :ios, '9.0' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } -end diff --git a/packages/path_provider/path_provider_ios/lib/messages.g.dart b/packages/path_provider/path_provider_ios/lib/messages.g.dart deleted file mode 100644 index 1914119b8bd8..000000000000 --- a/packages/path_provider/path_provider_ios/lib/messages.g.dart +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v3.1.5), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name -// @dart = 2.12 -import 'dart:async'; -import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; - -import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; -import 'package:flutter/services.dart'; - -class _PathProviderApiCodec extends StandardMessageCodec { - const _PathProviderApiCodec(); -} - -class PathProviderApi { - /// Constructor for [PathProviderApi]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - PathProviderApi({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; - - final BinaryMessenger? _binaryMessenger; - - static const MessageCodec codec = _PathProviderApiCodec(); - - Future getTemporaryPath() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getTemporaryPath', codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return (replyMap['result'] as String?); - } - } - - Future getApplicationSupportPath() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath', codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return (replyMap['result'] as String?); - } - } - - Future getLibraryPath() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getLibraryPath', codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return (replyMap['result'] as String?); - } - } - - Future getApplicationDocumentsPath() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath', codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return (replyMap['result'] as String?); - } - } -} diff --git a/packages/path_provider/path_provider_ios/pubspec.yaml b/packages/path_provider/path_provider_ios/pubspec.yaml deleted file mode 100644 index 2f6171cd70bd..000000000000 --- a/packages/path_provider/path_provider_ios/pubspec.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: path_provider_ios -description: iOS implementation of the path_provider plugin. -repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_ios -issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.0.11 - -environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" - -flutter: - plugin: - implements: path_provider - platforms: - ios: - pluginClass: FLTPathProviderPlugin - dartPluginClass: PathProviderIOS - -dependencies: - flutter: - sdk: flutter - path_provider_platform_interface: ^2.0.0 - -dev_dependencies: - flutter_driver: - sdk: flutter - flutter_test: - sdk: flutter - integration_test: - sdk: flutter - path: ^1.8.0 - pigeon: ^3.1.5 - plugin_platform_interface: ^2.0.0 - test: ^1.16.0 diff --git a/packages/path_provider/path_provider_ios/test/messages_test.g.dart b/packages/path_provider/path_provider_ios/test/messages_test.g.dart deleted file mode 100644 index d1c9ff88dca1..000000000000 --- a/packages/path_provider/path_provider_ios/test/messages_test.g.dart +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v3.1.5), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis -// ignore_for_file: avoid_relative_lib_imports -// @dart = 2.12 -import 'dart:async'; -import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; -import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../lib/messages.g.dart'; - -class _TestPathProviderApiCodec extends StandardMessageCodec { - const _TestPathProviderApiCodec(); -} - -abstract class TestPathProviderApi { - static const MessageCodec codec = _TestPathProviderApiCodec(); - - String? getTemporaryPath(); - String? getApplicationSupportPath(); - String? getLibraryPath(); - String? getApplicationDocumentsPath(); - static void setup(TestPathProviderApi? api, - {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getTemporaryPath', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - // ignore message - final String? output = api.getTemporaryPath(); - return {'result': output}; - }); - } - } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - // ignore message - final String? output = api.getApplicationSupportPath(); - return {'result': output}; - }); - } - } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getLibraryPath', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - // ignore message - final String? output = api.getLibraryPath(); - return {'result': output}; - }); - } - } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath', - codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - // ignore message - final String? output = api.getApplicationDocumentsPath(); - return {'result': output}; - }); - } - } - } -} diff --git a/packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart b/packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart deleted file mode 100644 index 16a7cd8d71a2..000000000000 --- a/packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider_ios/path_provider_ios.dart'; -import 'messages_test.g.dart'; - -class _Api implements TestPathProviderApi { - String? applicationDocumentsPath; - String? applicationSupportPath; - String? libraryPath; - String? temporaryPath; - - @override - String? getApplicationDocumentsPath() => applicationDocumentsPath; - - @override - String? getApplicationSupportPath() => applicationSupportPath; - - @override - String? getLibraryPath() => libraryPath; - - @override - String? getTemporaryPath() => temporaryPath; -} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('PathProviderIOS', () { - late PathProviderIOS pathProvider; - // These unit tests use the actual filesystem, since an injectable - // filesystem would add a runtime dependency to the package, so everything - // is contained to a temporary directory. - late Directory testRoot; - - late String temporaryPath; - late String applicationSupportPath; - late String libraryPath; - late String applicationDocumentsPath; - late _Api api; - - setUp(() async { - pathProvider = PathProviderIOS(); - - testRoot = Directory.systemTemp.createTempSync(); - final String basePath = testRoot.path; - temporaryPath = p.join(basePath, 'temporary', 'path'); - applicationSupportPath = - p.join(basePath, 'application', 'support', 'path'); - libraryPath = p.join(basePath, 'library', 'path'); - applicationDocumentsPath = - p.join(basePath, 'application', 'documents', 'path'); - - api = _Api(); - api.applicationDocumentsPath = applicationDocumentsPath; - api.applicationSupportPath = applicationSupportPath; - api.libraryPath = libraryPath; - api.temporaryPath = temporaryPath; - TestPathProviderApi.setup(api); - }); - - tearDown(() { - testRoot.deleteSync(recursive: true); - }); - - test('getTemporaryPath', () async { - final String? path = await pathProvider.getTemporaryPath(); - expect(path, temporaryPath); - }); - - test('getApplicationSupportPath', () async { - final String? path = await pathProvider.getApplicationSupportPath(); - expect(path, applicationSupportPath); - }); - - test('getApplicationSupportPath creates the directory if necessary', - () async { - final String? path = await pathProvider.getApplicationSupportPath(); - expect(Directory(path!).existsSync(), isTrue); - }); - - test('getLibraryPath', () async { - final String? path = await pathProvider.getLibraryPath(); - expect(path, libraryPath); - }); - - test('getApplicationDocumentsPath', () async { - final String? path = await pathProvider.getApplicationDocumentsPath(); - expect(path, applicationDocumentsPath); - }); - - test('getDownloadsPath throws', () async { - expect(pathProvider.getDownloadsPath(), throwsA(isUnsupportedError)); - }); - - test('getExternalCachePaths throws', () async { - expect(pathProvider.getExternalCachePaths(), throwsA(isUnsupportedError)); - }); - - test('getExternalStoragePath throws', () async { - expect( - pathProvider.getExternalStoragePath(), throwsA(isUnsupportedError)); - }); - - test('getExternalStoragePaths throws', () async { - expect( - pathProvider.getExternalStoragePaths(), throwsA(isUnsupportedError)); - }); - }); -} diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md index baf3283348de..d18e565f83f4 100644 --- a/packages/path_provider/path_provider_linux/CHANGELOG.md +++ b/packages/path_provider/path_provider_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.1.7 diff --git a/packages/path_provider/path_provider_linux/example/pubspec.yaml b/packages/path_provider/path_provider_linux/example/pubspec.yaml index 8d8940ba2f05..a305575bb13b 100644 --- a/packages/path_provider/path_provider_linux/example/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml index 41d587360b5e..b0d86fed090e 100644 --- a/packages/path_provider/path_provider_linux/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.7 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_macos/CHANGELOG.md b/packages/path_provider/path_provider_macos/CHANGELOG.md deleted file mode 100644 index 61727e4d364b..000000000000 --- a/packages/path_provider/path_provider_macos/CHANGELOG.md +++ /dev/null @@ -1,91 +0,0 @@ -## NEXT - -* Updates minimum Flutter version to 2.10. - -## 2.0.6 - -* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors - lint warnings. - -## 2.0.5 - -* Removes dependency on `meta`. - -## 2.0.4 - -* Switches to a package-internal implementation of the platform interface. - -## 2.0.3 - -* Fixes link in README. - -## 2.0.2 - -* Add Swift language version to podspec. -* Add native unit tests. -* Updated installation instructions in README. - -## 2.0.1 - -* Add `implements` to pubspec.yaml. - -## 2.0.0 - -* Update Dart SDK constraint for null safety compatibility. - -## 0.0.4+9 - -* Remove placeholder Dart file. - -## 0.0.4+8 - -* Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. - -## 0.0.4+7 - -* Update Flutter SDK constraint. - -## 0.0.4+6 - -* Remove unused `test` dependency. -* Update Dart SDK constraint in example. - -## 0.0.4+5 - -* Update license header. - -## 0.0.4+4 - -* Remove no-op android folder in the example app. - -## 0.0.4+3 - -* Remove Android folder from `path_provider_macos`. - -## 0.0.4+2 - -* Declare API stability and compatibility with `1.0.0` (more details at: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0). - -## 0.0.4+1 - -* Fix CocoaPods podspec lint warnings. - -## 0.0.4 - -* Adds an example app to run integration tests. - -## 0.0.3+1 - -* Make the pedantic `dev_dependency` explicit. - -## 0.0.3 - -* Added support for user's downloads directory. - -## 0.0.2+1 - -* Update README. - -## 0.0.1 - -* Initial open source release. diff --git a/packages/path_provider/path_provider_macos/README.md b/packages/path_provider/path_provider_macos/README.md deleted file mode 100644 index 6641134aefd9..000000000000 --- a/packages/path_provider/path_provider_macos/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# path\_provider\_macos - -The macos implementation of [`path_provider`][1]. - -## Usage - -This package is [endorsed][2], which means you can simply use `path_provider` -normally. This package will be automatically included in your app when you do. - -[1]: https://pub.dev/packages/path_provider -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin diff --git a/packages/path_provider/path_provider_macos/lib/path_provider_macos.dart b/packages/path_provider/path_provider_macos/lib/path_provider_macos.dart deleted file mode 100644 index 5dc3176e9b89..000000000000 --- a/packages/path_provider/path_provider_macos/lib/path_provider_macos.dart +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:flutter/foundation.dart' show visibleForTesting; -import 'package:flutter/services.dart'; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; - -/// The macOS implementation of [PathProviderPlatform]. -class PathProviderMacOS extends PathProviderPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - MethodChannel methodChannel = - const MethodChannel('plugins.flutter.io/path_provider_macos'); - - /// Registers this class as the default instance of [PathProviderPlatform] - static void registerWith() { - PathProviderPlatform.instance = PathProviderMacOS(); - } - - @override - Future getTemporaryPath() { - return methodChannel.invokeMethod('getTemporaryDirectory'); - } - - @override - Future getApplicationSupportPath() async { - final String? path = await methodChannel - .invokeMethod('getApplicationSupportDirectory'); - if (path != null) { - // Ensure the directory exists before returning it, for consistency with - // other platforms. - await Directory(path).create(recursive: true); - } - return path; - } - - @override - Future getLibraryPath() { - return methodChannel.invokeMethod('getLibraryDirectory'); - } - - @override - Future getApplicationDocumentsPath() { - return methodChannel - .invokeMethod('getApplicationDocumentsDirectory'); - } - - @override - Future getExternalStoragePath() async { - throw UnsupportedError('getExternalStoragePath is not supported on macOS'); - } - - @override - Future?> getExternalCachePaths() async { - throw UnsupportedError('getExternalCachePaths is not supported on macOS'); - } - - @override - Future?> getExternalStoragePaths({ - StorageDirectory? type, - }) async { - throw UnsupportedError('getExternalStoragePaths is not supported on macOS'); - } - - @override - Future getDownloadsPath() { - return methodChannel.invokeMethod('getDownloadsDirectory'); - } -} diff --git a/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift deleted file mode 100644 index e138eee759ac..000000000000 --- a/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import FlutterMacOS -import Foundation - -public class PathProviderPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "plugins.flutter.io/path_provider_macos", - binaryMessenger: registrar.messenger) - let instance = PathProviderPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getTemporaryDirectory": - result(getDirectory(ofType: FileManager.SearchPathDirectory.cachesDirectory)) - case "getApplicationDocumentsDirectory": - result(getDirectory(ofType: FileManager.SearchPathDirectory.documentDirectory)) - case "getApplicationSupportDirectory": - var path = getDirectory(ofType: FileManager.SearchPathDirectory.applicationSupportDirectory) - if let basePath = path { - let basePathURL = URL.init(fileURLWithPath: basePath) - path = basePathURL.appendingPathComponent(Bundle.main.bundleIdentifier!).path - } - result(path) - case "getLibraryDirectory": - result(getDirectory(ofType: FileManager.SearchPathDirectory.libraryDirectory)) - case "getDownloadsDirectory": - result(getDirectory(ofType: FileManager.SearchPathDirectory.downloadsDirectory)) - default: - result(FlutterMethodNotImplemented) - } - } -} - -/// Returns the user-domain director of the given type. -private func getDirectory(ofType directory: FileManager.SearchPathDirectory) -> String? { - let paths = NSSearchPathForDirectoriesInDomains( - directory, - FileManager.SearchPathDomainMask.userDomainMask, - true) - return paths.first -} diff --git a/packages/path_provider/path_provider_macos/macos/path_provider_macos.podspec b/packages/path_provider/path_provider_macos/macos/path_provider_macos.podspec deleted file mode 100644 index 14c468231f8c..000000000000 --- a/packages/path_provider/path_provider_macos/macos/path_provider_macos.podspec +++ /dev/null @@ -1,22 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# -Pod::Spec.new do |s| - s.name = 'path_provider_macos' - s.version = '0.0.1' - s.summary = 'A macOS implementation of the path_provider plugin.' - s.description = <<-DESC - A macOS implementation of the Flutter plugin for getting commonly used locations on the filesystem. - DESC - s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_macos' - s.license = { :type => 'BSD', :file => '../LICENSE' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_macos' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - - s.platform = :osx - s.osx.deployment_target = '10.11' - s.swift_version = '5.0' -end - diff --git a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md index f12e1ec53ade..e3470dc36844 100644 --- a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md +++ b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.5 * Updates imports for `prefer_relative_imports`. diff --git a/packages/path_provider/path_provider_platform_interface/pubspec.yaml b/packages/path_provider/path_provider_platform_interface/pubspec.yaml index 6ce7ec662b33..3ce20f6f85db 100644 --- a/packages/path_provider/path_provider_platform_interface/pubspec.yaml +++ b/packages/path_provider/path_provider_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.0.5 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_windows/CHANGELOG.md b/packages/path_provider/path_provider_windows/CHANGELOG.md index 757f13dbb533..08920a9569e8 100644 --- a/packages/path_provider/path_provider_windows/CHANGELOG.md +++ b/packages/path_provider/path_provider_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.3 * Updates minimum Flutter version to 2.10. diff --git a/packages/path_provider/path_provider_windows/example/pubspec.yaml b/packages/path_provider/path_provider_windows/example/pubspec.yaml index d70a4a84f504..306f20c354df 100644 --- a/packages/path_provider/path_provider_windows/example/pubspec.yaml +++ b/packages/path_provider/path_provider_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md index 0b5a6b63a52f..93e45c814668 100644 --- a/packages/plugin_platform_interface/CHANGELOG.md +++ b/packages/plugin_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported Dart version. + ## 2.1.3 * Minor fixes for new analysis options. diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml index 6a4bc488693b..25189d942f84 100644 --- a/packages/plugin_platform_interface/pubspec.yaml +++ b/packages/plugin_platform_interface/pubspec.yaml @@ -18,7 +18,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.1.3 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: meta: ^1.3.0 diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md index 7d1881596255..0787c27014f1 100644 --- a/packages/quick_actions/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/quick_actions/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.1 * Updates implementaion package versions to current versions. diff --git a/packages/quick_actions/quick_actions/example/pubspec.yaml b/packages/quick_actions/quick_actions/example/pubspec.yaml index 1a10a653db06..c629384ee5e2 100644 --- a/packages/quick_actions/quick_actions/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index 08b486fe50e3..3f1bf57a70f0 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index bc809a4dc477..6587627b2145 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.0 * Updates version to 1.0 to reflect current status. diff --git a/packages/quick_actions/quick_actions_android/android/build.gradle b/packages/quick_actions/quick_actions_android/android/build.gradle index e4cdec819ec9..e096e907ab70 100644 --- a/packages/quick_actions/quick_actions_android/android/build.gradle +++ b/packages/quick_actions/quick_actions_android/android/build.gradle @@ -37,7 +37,7 @@ android { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' } compileOptions { diff --git a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle index 666194bc11b0..c9cbddb9ffeb 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle +++ b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle @@ -62,6 +62,6 @@ dependencies { androidTestImplementation "androidx.test:runner:$androidXTestVersion" androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'androidx.test.ext:junit:1.0.0' - androidTestImplementation 'org.mockito:mockito-core:4.7.0' - androidTestImplementation 'org.mockito:mockito-android:4.7.0' + androidTestImplementation 'org.mockito:mockito-core:5.0.0' + androidTestImplementation 'org.mockito:mockito-android:5.0.0' } diff --git a/packages/quick_actions/quick_actions_android/example/pubspec.yaml b/packages/quick_actions/quick_actions_android/example/pubspec.yaml index c560d4dd5f1e..48a6fe9fd1a5 100644 --- a/packages/quick_actions/quick_actions_android/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index e47a1fdc13e9..038c8631287f 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md index bded35478899..e135fa4c9b69 100644 --- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.2 * Migrates remaining components to Swift and removes all Objective-C settings. diff --git a/packages/quick_actions/quick_actions_ios/example/pubspec.yaml b/packages/quick_actions/quick_actions_ios/example/pubspec.yaml index ecac371720d6..af0697022ea3 100644 --- a/packages/quick_actions/quick_actions_ios/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml index 6e7fb43dd7ed..2b7572368773 100644 --- a/packages/quick_actions/quick_actions_ios/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.2 environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md b/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md index 950864f96653..6bbfd5a35f67 100644 --- a/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.3 * Updates imports for `prefer_relative_imports`. diff --git a/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml b/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml index 2990da603c14..cfde0a76f5b2 100644 --- a/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml +++ b/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 1.0.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index b719ff158ff4..ed44436dfe1e 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,5 +1,15 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 2.0.17 + +* Updates code for stricter lint checks. + +## 2.0.16 + +* Switches to the new `shared_preferences_foundation` implementation package + for iOS and macOS. * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. diff --git a/packages/shared_preferences/shared_preferences/example/lib/main.dart b/packages/shared_preferences/shared_preferences/example/lib/main.dart index a2e72b446925..f9690395f10d 100644 --- a/packages/shared_preferences/shared_preferences/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences/example/lib/main.dart @@ -66,9 +66,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences/example/pubspec.yaml b/packages/shared_preferences/shared_preferences/example/pubspec.yaml index 6964656d16ef..944538da0d0c 100644 --- a/packages/shared_preferences/shared_preferences/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index 7a310d9a584a..30ee569c3ad3 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android. repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.0.15 +version: 2.0.17 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -15,11 +15,11 @@ flutter: android: default_package: shared_preferences_android ios: - default_package: shared_preferences_ios + default_package: shared_preferences_foundation linux: default_package: shared_preferences_linux macos: - default_package: shared_preferences_macos + default_package: shared_preferences_foundation web: default_package: shared_preferences_web windows: @@ -29,9 +29,8 @@ dependencies: flutter: sdk: flutter shared_preferences_android: ^2.0.8 - shared_preferences_ios: ^2.0.8 + shared_preferences_foundation: ^2.1.0 shared_preferences_linux: ^2.0.1 - shared_preferences_macos: ^2.0.0 shared_preferences_platform_interface: ^2.0.0 shared_preferences_web: ^2.0.0 shared_preferences_windows: ^2.0.1 diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index d9d7bbd82f46..727f2b626d81 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.0.15 + +* Updates code for stricter lint checks. + ## 2.0.14 * Fixes typo in `SharedPreferencesAndroid` docs. diff --git a/packages/shared_preferences/shared_preferences_android/android/build.gradle b/packages/shared_preferences/shared_preferences_android/android/build.gradle index feae770a475d..480e815d06f1 100644 --- a/packages/shared_preferences/shared_preferences_android/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/android/build.gradle @@ -44,7 +44,7 @@ android { } dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' } diff --git a/packages/shared_preferences/shared_preferences_android/example/lib/main.dart b/packages/shared_preferences/shared_preferences_android/example/lib/main.dart index bb513b09f6d5..cbcad6391beb 100644 --- a/packages/shared_preferences/shared_preferences_android/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_android/example/lib/main.dart @@ -70,9 +70,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml index bd1272c71d80..c0bc6668e3dd 100644 --- a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index 7692c114bfce..d968dcbce55b 100644 --- a/packages/shared_preferences/shared_preferences_android/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/pubspec.yaml @@ -2,11 +2,11 @@ name: shared_preferences_android description: Android implementation of the shared_preferences plugin repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.0.14 +version: 2.0.15 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart index d9c921378170..fb6764893651 100644 --- a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart +++ b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart @@ -33,13 +33,20 @@ void main() { setUp(() async { testData = InMemorySharedPreferencesStore.empty(); + Map getArgumentDictionary(MethodCall call) { + return (call.arguments as Map) + .cast(); + } + channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); if (methodCall.method == 'getAll') { return testData.getAll(); } if (methodCall.method == 'remove') { - final String key = methodCall.arguments['key']! as String; + final Map arguments = + getArgumentDictionary(methodCall); + final String key = arguments['key']! as String; return testData.remove(key); } if (methodCall.method == 'clear') { @@ -49,8 +56,10 @@ void main() { final Match? match = setterRegExp.matchAsPrefix(methodCall.method); if (match?.groupCount == 1) { final String valueType = match!.group(1)!; - final String key = methodCall.arguments['key'] as String; - final Object value = methodCall.arguments['value'] as Object; + final Map arguments = + getArgumentDictionary(methodCall); + final String key = arguments['key']! as String; + final Object value = arguments['value']!; return testData.setValue(valueType, key, value); } fail('Unexpected method call: ${methodCall.method}'); diff --git a/packages/path_provider/path_provider_macos/AUTHORS b/packages/shared_preferences/shared_preferences_foundation/AUTHORS similarity index 100% rename from packages/path_provider/path_provider_macos/AUTHORS rename to packages/shared_preferences/shared_preferences_foundation/AUTHORS diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md new file mode 100644 index 000000000000..b178143ca0b8 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md @@ -0,0 +1,19 @@ +## 2.1.3 + +* Uses the new `sharedDarwinSource` flag when available. +* Updates minimum Flutter version to 3.0. + +## 2.1.2 + +* Updates code for stricter lint checks. + +## 2.1.1 + +* Adds Swift runtime search paths in podspec to avoid crash in Objective-C apps. + Convert example app to Objective-C to catch future Swift runtime issues. + +## 2.1.0 + +* Renames the package previously published as + [`shared_preferences_macos`](https://pub.dev/packages/shared_preferences_macos) +* Adds iOS support. diff --git a/packages/path_provider/path_provider_macos/LICENSE b/packages/shared_preferences/shared_preferences_foundation/LICENSE similarity index 100% rename from packages/path_provider/path_provider_macos/LICENSE rename to packages/shared_preferences/shared_preferences_foundation/LICENSE diff --git a/packages/shared_preferences/shared_preferences_ios/README.md b/packages/shared_preferences/shared_preferences_foundation/README.md similarity index 77% rename from packages/shared_preferences/shared_preferences_ios/README.md rename to packages/shared_preferences/shared_preferences_foundation/README.md index 5c9ced3b2096..1aaa9253399b 100644 --- a/packages/shared_preferences/shared_preferences_ios/README.md +++ b/packages/shared_preferences/shared_preferences_foundation/README.md @@ -1,6 +1,6 @@ -# shared\_preferences\_ios +# shared\_preferences\_foundation -The iOS implementation of [`shared_preferences`][1]. +The iOS and macOS implementation of [`shared_preferences`][1]. ## Usage diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift new file mode 100644 index 000000000000..c97698ce0f7c --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Foundation + +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#endif + +public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi { + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = SharedPreferencesPlugin() + // Workaround for https://github.com/flutter/flutter/issues/118103. +#if os(iOS) + let messenger = registrar.messenger() +#else + let messenger = registrar.messenger +#endif + UserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance) + } + + func getAll() -> [String? : Any?] { + return getAllPrefs(); + } + + func setBool(key: String, value: Bool) { + UserDefaults.standard.set(value, forKey: key) + } + + func setDouble(key: String, value: Double) { + UserDefaults.standard.set(value, forKey: key) + } + + func setValue(key: String, value: Any) { + UserDefaults.standard.set(value, forKey: key) + } + + func remove(key: String) { + UserDefaults.standard.removeObject(forKey: key) + } + + func clear() { + let defaults = UserDefaults.standard + for (key, _) in getAllPrefs() { + defaults.removeObject(forKey: key) + } + } +} + +/// Returns all preferences stored by this plugin. +private func getAllPrefs() -> [String: Any] { + var filteredPrefs: [String: Any] = [:] + if let appDomain = Bundle.main.bundleIdentifier, + let prefs = UserDefaults.standard.persistentDomain(forName: appDomain) + { + for (key, value) in prefs where key.hasPrefix("flutter.") { + filteredPrefs[key] = value + } + } + return filteredPrefs +} diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift new file mode 100644 index 000000000000..933217b7bf96 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift @@ -0,0 +1,111 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#else +#error("Unsupported platform.") +#endif + + +/// Generated class from Pigeon. +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol UserDefaultsApi { + func remove(key: String) + func setBool(key: String, value: Bool) + func setDouble(key: String, value: Double) + func setValue(key: String, value: Any) + func getAll() -> [String?: Any?] + func clear() +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class UserDefaultsApiSetup { + /// The codec used by UserDefaultsApi. + /// Sets up an instance of `UserDefaultsApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UserDefaultsApi?) { + let removeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.remove", binaryMessenger: binaryMessenger) + if let api = api { + removeChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let keyArg = args[0] as! String + api.remove(key: keyArg) + reply(wrapResult(nil)) + } + } else { + removeChannel.setMessageHandler(nil) + } + let setBoolChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setBool", binaryMessenger: binaryMessenger) + if let api = api { + setBoolChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let keyArg = args[0] as! String + let valueArg = args[1] as! Bool + api.setBool(key: keyArg, value: valueArg) + reply(wrapResult(nil)) + } + } else { + setBoolChannel.setMessageHandler(nil) + } + let setDoubleChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setDouble", binaryMessenger: binaryMessenger) + if let api = api { + setDoubleChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let keyArg = args[0] as! String + let valueArg = args[1] as! Double + api.setDouble(key: keyArg, value: valueArg) + reply(wrapResult(nil)) + } + } else { + setDoubleChannel.setMessageHandler(nil) + } + let setValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.setValue", binaryMessenger: binaryMessenger) + if let api = api { + setValueChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let keyArg = args[0] as! String + let valueArg = args[1]! + api.setValue(key: keyArg, value: valueArg) + reply(wrapResult(nil)) + } + } else { + setValueChannel.setMessageHandler(nil) + } + let getAllChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.getAll", binaryMessenger: binaryMessenger) + if let api = api { + getAllChannel.setMessageHandler { _, reply in + let result = api.getAll() + reply(wrapResult(result)) + } + } else { + getAllChannel.setMessageHandler(nil) + } + let clearChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.UserDefaultsApi.clear", binaryMessenger: binaryMessenger) + if let api = api { + clearChannel.setMessageHandler { _, reply in + api.clear() + reply(wrapResult(nil)) + } + } else { + clearChannel.setMessageHandler(nil) + } + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: FlutterError) -> [Any?] { + return [ + error.code, + error.message, + error.details + ] +} diff --git a/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift b/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift new file mode 100644 index 000000000000..a4dd4b58f923 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#endif + +@testable import shared_preferences_foundation + +class RunnerTests: XCTestCase { + func testSetAndGet() throws { + let plugin = SharedPreferencesPlugin() + + plugin.setBool(key: "flutter.aBool", value: true) + plugin.setDouble(key: "flutter.aDouble", value: 3.14) + plugin.setValue(key: "flutter.anInt", value: 42) + plugin.setValue(key: "flutter.aString", value: "hello world") + plugin.setValue(key: "flutter.aStringList", value: ["hello", "world"]) + + let storedValues = plugin.getAll() + XCTAssertEqual(storedValues["flutter.aBool"] as? Bool, true) + XCTAssertEqual(storedValues["flutter.aDouble"] as! Double, 3.14, accuracy: 0.0001) + XCTAssertEqual(storedValues["flutter.anInt"] as? Int, 42) + XCTAssertEqual(storedValues["flutter.aString"] as? String, "hello world") + XCTAssertEqual(storedValues["flutter.aStringList"] as? Array, ["hello", "world"]) + } + + func testRemove() throws { + let plugin = SharedPreferencesPlugin() + let testKey = "flutter.foo" + plugin.setValue(key: testKey, value: 42) + + // Make sure there is something to remove, so the test can't pass due to a set failure. + let preRemovalValues = plugin.getAll() + XCTAssertEqual(preRemovalValues[testKey] as? Int, 42) + + // Then verify that removing it works. + plugin.remove(key: testKey) + + let finalValues = plugin.getAll() + XCTAssertNil(finalValues[testKey] as Any?) + } + + func testClear() throws { + let plugin = SharedPreferencesPlugin() + let testKey = "flutter.foo" + plugin.setValue(key: testKey, value: 42) + + // Make sure there is something to clear, so the test can't pass due to a set failure. + let preRemovalValues = plugin.getAll() + XCTAssertEqual(preRemovalValues[testKey] as? Int, 42) + + // Then verify that clearing works. + plugin.clear() + + let finalValues = plugin.getAll() + XCTAssertNil(finalValues[testKey] as Any?) + } +} diff --git a/packages/shared_preferences/shared_preferences_macos/macos/shared_preferences_macos.podspec b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec similarity index 52% rename from packages/shared_preferences/shared_preferences_macos/macos/shared_preferences_macos.podspec rename to packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec index 590b0c34adcf..b645bb520bab 100644 --- a/packages/shared_preferences/shared_preferences_macos/macos/shared_preferences_macos.podspec +++ b/packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec @@ -2,22 +2,26 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| - s.name = 'shared_preferences_macos' + s.name = 'shared_preferences_foundation' s.version = '0.0.1' - s.summary = 'macOS implementation of the shared_preferences plugin.' + s.summary = 'iOS and macOS implementation of the shared_preferences plugin.' s.description = <<-DESC Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. DESC - s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_macos' + s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_macos' } + s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' } s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - - s.platform = :osx, '10.11' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } s.swift_version = '5.0' end - diff --git a/packages/shared_preferences/shared_preferences_ios/example/.gitignore b/packages/shared_preferences/shared_preferences_foundation/example/.gitignore similarity index 92% rename from packages/shared_preferences/shared_preferences_ios/example/.gitignore rename to packages/shared_preferences/shared_preferences_foundation/example/.gitignore index 0fa6b675c0a5..24476c5d1eb5 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/.gitignore +++ b/packages/shared_preferences/shared_preferences_foundation/example/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +migrate_working_dir/ # IntelliJ related *.iml @@ -31,9 +32,6 @@ .pub/ /build/ -# Web related -lib/generated_plugin_registrant.dart - # Symbolication related app.*.symbols diff --git a/packages/path_provider/path_provider_macos/example/README.md b/packages/shared_preferences/shared_preferences_foundation/example/README.md similarity index 100% rename from packages/path_provider/path_provider_macos/example/README.md rename to packages/shared_preferences/shared_preferences_foundation/example/README.md diff --git a/packages/shared_preferences/shared_preferences_macos/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart similarity index 98% rename from packages/shared_preferences/shared_preferences_macos/example/integration_test/shared_preferences_test.dart rename to packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart index a980eab26679..b3c1973c2cd5 100644 --- a/packages/shared_preferences/shared_preferences_macos/example/integration_test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart @@ -9,7 +9,7 @@ import 'package:shared_preferences_platform_interface/shared_preferences_platfor void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('SharedPreferencesMacOS', () { + group('SharedPreferencesFoundation', () { const Map kTestValues = { 'flutter.String': 'hello world', 'flutter.bool': true, diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/.gitignore b/packages/shared_preferences/shared_preferences_foundation/example/ios/.gitignore new file mode 100644 index 000000000000..7a7f9873ad7d --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/AppFrameworkInfo.plist b/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/AppFrameworkInfo.plist similarity index 86% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/AppFrameworkInfo.plist index 3a9c234f96d4..9625e105df39 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,11 +20,7 @@ ???? CFBundleVersion 1.0 - UIRequiredDeviceCapabilities - - arm64 - MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Debug.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000000..ec97fc6f3021 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Release.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000000..c4855bfe2000 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Podfile b/packages/shared_preferences/shared_preferences_foundation/example/ios/Podfile similarity index 95% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Podfile rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Podfile index 3924e59aa0f9..fdcc671eb341 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Podfile +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -28,6 +28,9 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + use_frameworks! + use_modular_headers! + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj similarity index 59% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj index c7567b312596..920741d8f335 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,24 +3,23 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ - 2D92224B1EC342E7007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92224A1EC342E7007564B0 /* GeneratedPluginRegistrant.m */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 4E8BDD90E81668641A750C18 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 556D8EFC85341B7D1FDF536D /* libPods-RunnerTests.a */; }; - 66F8BCECCEFF62F4071D2DFC /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E03CD14DABAA3AD3923183 /* libPods-Runner.a */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - F76AC2092669B6AE0040C8BC /* SharedPreferencesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC2082669B6AE0040C8BC /* SharedPreferencesTests.m */; }; + 9AF3A5CAB88B27F2BC36D686 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4C443E7E16FB2AD978AC1D6 /* Pods_RunnerTests.framework */; }; + B81650923B266CE1F32B75E4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3702C135032CE4599D8327B /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - F76AC20B2669B6AE0040C8BC /* PBXContainerItemProxy */ = { + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; @@ -43,60 +42,66 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 081A3238A89B77A99B096D83 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 12E03CD14DABAA3AD3923183 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2D9222491EC342E7007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 2D92224A1EC342E7007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../../../darwin/Tests/RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A8F2565F3AF472E2E0A219E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 556D8EFC85341B7D1FDF536D /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6F1615DD96BB2B955423149B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 942E815CEF30E101E045B849 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 87C77C652D5BC0B23F81E01F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A2FC4F1DC78D7C01312F877F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - D896CE48B6CC2EB7D42CB6B6 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - F76AC2062669B6AE0040C8BC /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - F76AC2082669B6AE0040C8BC /* SharedPreferencesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SharedPreferencesTests.m; sourceTree = ""; }; - F76AC20A2669B6AE0040C8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B5E9D2BAFD0E9BF6494E5389 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D4C443E7E16FB2AD978AC1D6 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D9DC9227831D288079E5C887 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + F1B6CB00204D3430428972D5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F3702C135032CE4599D8327B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { + 3F94D8484CE6A0609BCE7680 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 66F8BCECCEFF62F4071D2DFC /* libPods-Runner.a in Frameworks */, + 9AF3A5CAB88B27F2BC36D686 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - F76AC2032669B6AE0040C8BC /* Frameworks */ = { + 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4E8BDD90E81668641A750C18 /* libPods-RunnerTests.a in Frameworks */, + B81650923B266CE1F32B75E4 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - 942E815CEF30E101E045B849 /* Pods-Runner.debug.xcconfig */, - 081A3238A89B77A99B096D83 /* Pods-Runner.release.xcconfig */, - A2FC4F1DC78D7C01312F877F /* Pods-RunnerTests.debug.xcconfig */, - D896CE48B6CC2EB7D42CB6B6 /* Pods-RunnerTests.release.xcconfig */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - name = Pods; + path = RunnerTests; + sourceTree = ""; + }; + 4E1DD4374F34EBDF7F4214F0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F3702C135032CE4599D8327B /* Pods_Runner.framework */, + D4C443E7E16FB2AD978AC1D6 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -115,10 +120,10 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, - F76AC2072669B6AE0040C8BC /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, - 840012C8B5EDBCF56B0E4AC1 /* Pods */, - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, + DA43E4FDD6392A0D5FBF1611 /* Pods */, + 4E1DD4374F34EBDF7F4214F0 /* Frameworks */, ); sourceTree = ""; }; @@ -126,7 +131,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, - F76AC2062669B6AE0040C8BC /* RunnerTests.xctest */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -134,59 +139,66 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 2D9222491EC342E7007564B0 /* GeneratedPluginRegistrant.h */, - 2D92224A1EC342E7007564B0 /* GeneratedPluginRegistrant.m */, - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + DA43E4FDD6392A0D5FBF1611 /* Pods */ = { isa = PBXGroup; children = ( - 12E03CD14DABAA3AD3923183 /* libPods-Runner.a */, - 556D8EFC85341B7D1FDF536D /* libPods-RunnerTests.a */, + 3A8F2565F3AF472E2E0A219E /* Pods-Runner.debug.xcconfig */, + 87C77C652D5BC0B23F81E01F /* Pods-Runner.release.xcconfig */, + 6F1615DD96BB2B955423149B /* Pods-Runner.profile.xcconfig */, + F1B6CB00204D3430428972D5 /* Pods-RunnerTests.debug.xcconfig */, + D9DC9227831D288079E5C887 /* Pods-RunnerTests.release.xcconfig */, + B5E9D2BAFD0E9BF6494E5389 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Frameworks; - sourceTree = ""; - }; - F76AC2072669B6AE0040C8BC /* RunnerTests */ = { - isa = PBXGroup; - children = ( - F76AC2082669B6AE0040C8BC /* SharedPreferencesTests.m */, - F76AC20A2669B6AE0040C8BC /* Info.plist */, - ); - path = RunnerTests; + name = Pods; + path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 9DEF57700431B717ADF93FFA /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 3F94D8484CE6A0609BCE7680 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 4B0B07E2CB0088D1DE03E09A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3AC0A86331B4FD70A0EF91D9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -197,46 +209,27 @@ productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; - F76AC2052669B6AE0040C8BC /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = F76AC20F2669B6AE0040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - DB9B98025BDEFED85B1B62A7 /* [CP] Check Pods Manifest.lock */, - F76AC2022669B6AE0040C8BC /* Sources */, - F76AC2032669B6AE0040C8BC /* Frameworks */, - F76AC2042669B6AE0040C8BC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - F76AC20C2669B6AE0040C8BC /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = F76AC2062669B6AE0040C8BC /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1100; - ORGANIZATIONNAME = "The Flutter Authors"; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - }; - F76AC2052669B6AE0040C8BC = { - CreatedOnToolsVersion = 12.5; - ProvisioningStyle = Automatic; - TestTargetID = 97C146ED1CF9000F007C117D; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -249,71 +242,79 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, - F76AC2052669B6AE0040C8BC /* RunnerTests */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { + 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - F76AC2042669B6AE0040C8BC /* Resources */ = { + 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 3AC0A86331B4FD70A0EF91D9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Thin Binary"; - outputPaths = ( + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Run Script"; + name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + 4B0B07E2CB0088D1DE03E09A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); @@ -322,7 +323,22 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - DB9B98025BDEFED85B1B62A7 /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9DEF57700431B717ADF93FFA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -347,31 +363,30 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { + 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, - 2D92224B1EC342E7007564B0 /* GeneratedPluginRegistrant.m in Sources */, + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - F76AC2022669B6AE0040C8BC /* Sources */ = { + 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F76AC2092669B6AE0040C8BC /* SharedPreferencesTests.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - F76AC20C2669B6AE0040C8BC /* PBXTargetDependency */ = { + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = F76AC20B2669B6AE0040C8BC /* PBXContainerItemProxy */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -395,11 +410,131 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.sharedPreferencesFoundationExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F1B6CB00204D3430428972D5 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.sharedPreferencesFoundationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D9DC9227831D288079E5C887 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.sharedPreferencesFoundationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B5E9D2BAFD0E9BF6494E5389 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.sharedPreferencesFoundationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -443,7 +578,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -455,7 +590,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -493,9 +627,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -506,19 +643,20 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.sharedPreferencesExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.sharedPreferencesFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -527,76 +665,51 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.sharedPreferencesExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - F76AC20D2669B6AE0040C8BC /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A2FC4F1DC78D7C01312F877F /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; - }; - name = Debug; - }; - F76AC20E2669B6AE0040C8BC /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D896CE48B6CC2EB7D42CB6B6 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.sharedPreferencesFoundationExample; PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F76AC20F2669B6AE0040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - F76AC20D2669B6AE0040C8BC /* Debug */, - F76AC20E2669B6AE0040C8BC /* Release */, + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 94% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e29b432c48c..e42adcb34c2d 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + skipped = "NO" + parallelizable = "YES"> @@ -71,7 +72,7 @@ + + + + PreviewsEnabled + + + diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/AppDelegate.swift b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000000..caf998393333 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/path_provider/path_provider_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 94% rename from packages/path_provider/path_provider_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d22f10b2ab63..d36b1fab2d9d 100644 --- a/packages/path_provider/path_provider_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -107,6 +107,12 @@ "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" } ], "info" : { diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_xN#0001NP)t-s|Ns9~ z#rXRE|M&d=0au&!`~QyF`q}dRnBDt}*!qXo`c{v z{Djr|@Adh0(D_%#_&mM$D6{kE_x{oE{l@J5@%H*?%=t~i_`ufYOPkAEn!pfkr2$fs z652Tz0001XNklqeeKN4RM4i{jKqmiC$?+xN>3Apn^ z0QfuZLym_5b<*QdmkHjHlj811{If)dl(Z2K0A+ekGtrFJb?g|wt#k#pV-#A~bK=OT ts8>{%cPtyC${m|1#B1A6#u!Q;umknL1chzTM$P~L002ovPDHLkV1lTfnu!1a literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 zcmV;526g#~P){YQnis^a@{&-nmRmq)<&%Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b zE>q9${Lb$D&h7k=+4=!ek^n+`0zq>LL1O?lVyea53S5x`Nqqo2YyeuIrQrJj9XjOp z{;T5qbj3}&1vg1VK~#9!?b~^C5-}JC@Pyrv-6dSEqJqT}#j9#dJ@GzT@B8}x zU&J@bBI>f6w6en+CeI)3^kC*U?}X%OD8$Fd$H&LV$H&LV$H&LV#|K5~mLYf|VqzOc zkc7qL~0sOYuM{tG`rYEDV{DWY`Z8&)kW*hc2VkBuY+^Yx&92j&StN}Wp=LD zxoGxXw6f&8sB^u})h@b@z0RBeD`K7RMR9deyL(ZJu#39Z>rT)^>v}Khq8U-IbIvT> z?4pV9qGj=2)TNH3d)=De<+^w;>S7m_eFKTvzeaBeir45xY!^m!FmxnljbSS_3o=g( z->^wC9%qkR{kbGnW8MfFew_o9h3(r55Is`L$8KI@d+*%{=Nx+FXJ98L0PjFIu;rGnnfY zn1R5Qnp<{Jq0M1vX=X&F8gtLmcWv$1*M@4ZfF^9``()#hGTeKeP`1!iED ztNE(TN}M5}3Bbc*d=FIv`DNv&@|C6yYj{sSqUj5oo$#*0$7pu|Dd2TLI>t5%I zIa4Dvr(iayb+5x=j*Vum9&irk)xV1`t509lnPO0%skL8_1c#Xbamh(2@f?4yUI zhhuT5<#8RJhGz4%b$`PJwKPAudsm|at?u;*hGgnA zU1;9gnxVBC)wA(BsB`AW54N{|qmikJR*%x0c`{LGsSfa|NK61pYH(r-UQ4_JXd!Rsz)=k zL{GMc5{h138)fF5CzHEDM>+FqY)$pdN3}Ml+riTgJOLN0F*Vh?{9ESR{SVVg>*>=# zix;VJHPtvFFCRY$Ks*F;VX~%*r9F)W`PmPE9F!(&s#x07n2<}?S{(ygpXgX-&B&OM zONY&BRQ(#%0%jeQs?oJ4P!p*R98>qCy5p8w>_gpuh39NcOlp)(wOoz0sY-Qz55eB~ z7OC-fKBaD1sE3$l-6QgBJO!n?QOTza`!S_YK z_v-lm^7{VO^8Q@M_^8F)09Ki6%=s?2_5eupee(w1FB%aqSweusQ-T+CH0Xt{` zFjMvW{@C&TB)k25()nh~_yJ9coBRL(0oO@HK~z}7?bm5j;y@69;bvlHb2tf!$ReA~x{22wTq550 z?f?Hnw(;m3ip30;QzdV~7pi!wyMYhDtXW#cO7T>|f=bdFhu+F!zMZ2UFj;GUKX7tI z;hv3{q~!*pMj75WP_c}>6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FaF{8 z;u`Mw0ly(uE>*CgQYv{be6ab2LWhlaH1^iLIM{olnag$78^Fd}%dR7;JECQ+hmk|o z!u2&!3MqPfP5ChDSkFSH8F2WVOEf0(E_M(JL17G}Y+fg0_IuW%WQ zG(mG&u?|->YSdk0;8rc{yw2@2Z&GA}z{Wb91Ooz9VhA{b2DYE7RmG zjL}?eq#iX%3#k;JWMx_{^2nNax`xPhByFiDX+a7uTGU|otOvIAUy|dEKkXOm-`aWS z27pUzD{a)Ct<6p{{3)+lq@i`t@%>-wT4r?*S}k)58e09WZYP0{{R3FC5Sl00039P)t-s|Ns9~ z#rP?<_5oL$Q^olD{r_0T`27C={r>*`|Nj71npVa5OTzc(_WfbW_({R{p56NV{r*M2 z_xt?)2V0#0NsfV0u>{42ctGP(8vQj-Btk1n|O0ZD=YLwd&R{Ko41Gr9H= zY@z@@bOAMB5Ltl$E>bJJ{>JP30ZxkmI%?eW{k`b?Wy<&gOo;dS`~CR$Vwb@XWtR|N zi~t=w02?-0&j0TD{>bb6sNwsK*!p?V`RMQUl(*DVjk-9Cx+-z1KXab|Ka2oXhX5f% z`$|e!000AhNklrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`? zTG`AHia671e^vgmp!llKp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?hz91 z7p83F3%LVu9;S$tSI$C^%^yud1dfTM_6p2|+5Ejp$bd`GDvbR|xit>i!ZD&F>@CJrPmu*UjD&?DfZs=$@e3FQA(vNiU+$A*%a} z?`XcG2jDxJ_ZQ#Md`H{4Lpf6QBDp81_KWZ6Tk#yCy1)32zO#3<7>b`eT7UyYH1eGz z;O(rH$=QR*L%%ZcBpc=eGua?N55nD^K(8<#gl2+pN_j~b2MHs4#mcLmv%DkspS-3< zpI1F=^9siI0s-;IN_IrA;5xm~3?3!StX}pUv0vkxMaqm+zxrg7X7(I&*N~&dEd0kD z-FRV|g=|QuUsuh>-xCI}vD2imzYIOIdcCVV=$Bz@*u0+Bs<|L^)32nN*=wu3n%Ynw z@1|eLG>!8ruU1pFXUfb`j>(=Gy~?Rn4QJ-c3%3T|(Frd!bI`9u&zAnyFYTqlG#&J7 zAkD(jpw|oZLNiA>;>hgp1KX7-wxC~31II47gc zHcehD6Uxlf%+M^^uN5Wc*G%^;>D5qT{>=uxUhX%WJu^Z*(_Wq9y}npFO{Hhb>s6<9 zNi0pHXWFaVZnb)1+RS&F)xOv6&aeILcI)`k#0YE+?e)5&#r7J#c`3Z7x!LpTc01dx zrdC3{Z;joZ^KN&))zB_i)I9fWedoN>Zl-6_Iz+^G&*ak2jpF07*qoM6N<$f;w%0(f|Me literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000000..89c2725b70f1 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 51% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard index ebf48f603974..f2e259c7c939 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,8 @@ - + - + @@ -10,13 +10,20 @@ - - + + - - + + + + + + + + + @@ -24,4 +31,7 @@ + + + diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Base.lproj/Main.storyboard b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Info.plist b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Info.plist similarity index 78% rename from packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Info.plist rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Info.plist index 22fc4c23715d..30d5f4b0e845 100644 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Info.plist +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Info.plist @@ -3,7 +3,9 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Shared Preferences Foundation CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,25 +13,21 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - shared_preferences_example + shared_preferences_foundation_example CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - 1 + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main - UIRequiredDeviceCapabilities - - arm64 - UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -45,5 +43,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.h b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Runner-Bridging-Header.h similarity index 63% rename from packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.h rename to packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Runner-Bridging-Header.h index 8f9fd4597f9d..eb7e8ba8052f 100644 --- a/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.h +++ b/packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Runner-Bridging-Header.h @@ -2,7 +2,4 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import - -@interface FLTPathProviderPlugin : NSObject -@end +#import "GeneratedPluginRegistrant.h" diff --git a/packages/shared_preferences/shared_preferences_macos/example/lib/main.dart b/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart similarity index 95% rename from packages/shared_preferences/shared_preferences_macos/example/lib/main.dart rename to packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart index e6bbe5931471..a5aedd54ab6f 100644 --- a/packages/shared_preferences/shared_preferences_macos/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart @@ -72,9 +72,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Flutter/Flutter-Release.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Podfile b/packages/shared_preferences/shared_preferences_foundation/example/macos/Podfile similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Podfile rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Podfile diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner.xcodeproj/project.pbxproj b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/project.pbxproj similarity index 99% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/project.pbxproj index 96f46f062f91..0bfa5f0a93d7 100644 --- a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -78,7 +78,7 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 33EBD39826727BD10013E557 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 33EBD39A26727BD10013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 33EBD39A26727BD10013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../../../darwin/Tests/RunnerTests.swift; sourceTree = ""; }; 33EBD39C26727BD10013E557 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53F020549CA1E801ACA3428F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; @@ -260,7 +260,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -347,6 +347,7 @@ }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -389,11 +390,11 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/shared_preferences_macos/shared_preferences_macos.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 99% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 208a9bafa77a..6700d7ba4c05 100644 --- a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/AppDelegate.swift b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/AppDelegate.swift similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/AppDelegate.swift rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/AppDelegate.swift diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/Debug.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/Debug.xcconfig rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Debug.xcconfig diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/Release.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/Release.xcconfig rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Release.xcconfig diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/Warnings.xcconfig b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/DebugProfile.entitlements b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/DebugProfile.entitlements rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Info.plist b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Info.plist similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Info.plist rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Info.plist diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/MainFlutterWindow.swift b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/MainFlutterWindow.swift rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/MainFlutterWindow.swift diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Release.entitlements b/packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements similarity index 100% rename from packages/shared_preferences/shared_preferences_macos/example/macos/Runner/Release.entitlements rename to packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements diff --git a/packages/path_provider/path_provider_macos/example/macos/RunnerTests/Info.plist b/packages/shared_preferences/shared_preferences_foundation/example/macos/RunnerTests/Info.plist similarity index 100% rename from packages/path_provider/path_provider_macos/example/macos/RunnerTests/Info.plist rename to packages/shared_preferences/shared_preferences_foundation/example/macos/RunnerTests/Info.plist diff --git a/packages/shared_preferences/shared_preferences_macos/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml similarity index 78% rename from packages/shared_preferences/shared_preferences_macos/example/pubspec.yaml rename to packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml index f650fb7a6268..ef67f234e7c5 100644 --- a/packages/shared_preferences/shared_preferences_macos/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml @@ -1,17 +1,17 @@ name: shared_preferences_example -description: Demonstrates how to use the shared_preferences plugin. +description: Testbed for the shared_preferences_foundation implementation. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: sdk: flutter - shared_preferences_macos: + shared_preferences_foundation: # When depending on this package from a real application you should use: - # shared_preferences_macos: ^x.y.z + # shared_preferences_foundation: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. diff --git a/packages/path_provider/path_provider_macos/example/test_driver/integration_test.dart b/packages/shared_preferences/shared_preferences_foundation/example/test_driver/integration_test.dart similarity index 100% rename from packages/path_provider/path_provider_macos/example/test_driver/integration_test.dart rename to packages/shared_preferences/shared_preferences_foundation/example/test_driver/integration_test.dart diff --git a/packages/shared_preferences/shared_preferences_foundation/ios/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_foundation/ios/Classes/SharedPreferencesPlugin.swift new file mode 120000 index 000000000000..7b5941d0dc67 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/ios/Classes/SharedPreferencesPlugin.swift @@ -0,0 +1 @@ +../../darwin/Classes/SharedPreferencesPlugin.swift \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_foundation/ios/Classes/messages.g.swift b/packages/shared_preferences/shared_preferences_foundation/ios/Classes/messages.g.swift new file mode 120000 index 000000000000..11bcf06e96a8 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/ios/Classes/messages.g.swift @@ -0,0 +1 @@ +../../darwin/Classes/messages.g.swift \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_foundation/ios/README.md b/packages/shared_preferences/shared_preferences_foundation/ios/README.md new file mode 100644 index 000000000000..fd7261950f35 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/ios/README.md @@ -0,0 +1,4 @@ +This only contains symlinks to ../darwin, to support versions of Flutter +prior that don't include https://github.com/flutter/flutter/pull/115337. +Once the minimum Flutter version supported by this implementation is one that +includes that functionality, this directory should be removed. diff --git a/packages/shared_preferences/shared_preferences_foundation/ios/shared_preferences_foundation.podspec b/packages/shared_preferences/shared_preferences_foundation/ios/shared_preferences_foundation.podspec new file mode 120000 index 000000000000..59dcc19e0bf9 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/ios/shared_preferences_foundation.podspec @@ -0,0 +1 @@ +../darwin/shared_preferences_foundation.podspec \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_ios/lib/messages.g.dart b/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart similarity index 53% rename from packages/shared_preferences/shared_preferences_ios/lib/messages.g.dart rename to packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart index 0e76291fb655..f7c6c21567d2 100644 --- a/packages/shared_preferences/shared_preferences_ios/lib/messages.g.dart +++ b/packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart @@ -1,50 +1,41 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v1.0.16), do not edit directly. +// Autogenerated from Pigeon (v5.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name -// @dart = 2.12 +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import import 'dart:async'; -import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; -import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; -class _UserDefaultsApiCodec extends StandardMessageCodec { - const _UserDefaultsApiCodec(); -} - class UserDefaultsApi { /// Constructor for [UserDefaultsApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. UserDefaultsApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _UserDefaultsApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future remove(String arg_key) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.remove', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_key]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_key]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', - details: null, ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -55,21 +46,18 @@ class UserDefaultsApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setBool', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_key, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', - details: null, ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -80,21 +68,18 @@ class UserDefaultsApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setDouble', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_key, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', - details: null, ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -105,21 +90,18 @@ class UserDefaultsApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setValue', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_key, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_key, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', - details: null, ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -130,25 +112,25 @@ class UserDefaultsApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.getAll', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', - details: null, ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as Map?)! - .cast(); + return (replyList[0] as Map?)!.cast(); } } @@ -156,21 +138,17 @@ class UserDefaultsApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.clear', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', - details: null, ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; diff --git a/packages/shared_preferences/shared_preferences_ios/lib/shared_preferences_ios.dart b/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart similarity index 85% rename from packages/shared_preferences/shared_preferences_ios/lib/shared_preferences_ios.dart rename to packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart index 10638840804e..46b0ec41ea80 100644 --- a/packages/shared_preferences/shared_preferences_ios/lib/shared_preferences_ios.dart +++ b/packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart @@ -8,8 +8,8 @@ import 'messages.g.dart'; typedef _Setter = Future Function(String key, Object value); -/// iOS implementation of shared_preferences. -class SharedPreferencesIOS extends SharedPreferencesStorePlatform { +/// iOS and macOS implementation of shared_preferences. +class SharedPreferencesFoundation extends SharedPreferencesStorePlatform { final UserDefaultsApi _api = UserDefaultsApi(); late final Map _setters = { 'Bool': (String key, Object value) { @@ -29,9 +29,10 @@ class SharedPreferencesIOS extends SharedPreferencesStorePlatform { }, }; - /// Registers this class as the default instance of [PathProviderPlatform]. + /// Registers this class as the default instance of + /// [SharedPreferencesStorePlatform]. static void registerWith() { - SharedPreferencesStorePlatform.instance = SharedPreferencesIOS(); + SharedPreferencesStorePlatform.instance = SharedPreferencesFoundation(); } @override diff --git a/packages/shared_preferences/shared_preferences_foundation/macos/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_foundation/macos/Classes/SharedPreferencesPlugin.swift new file mode 120000 index 000000000000..7b5941d0dc67 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/macos/Classes/SharedPreferencesPlugin.swift @@ -0,0 +1 @@ +../../darwin/Classes/SharedPreferencesPlugin.swift \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_foundation/macos/Classes/messages.g.swift b/packages/shared_preferences/shared_preferences_foundation/macos/Classes/messages.g.swift new file mode 120000 index 000000000000..11bcf06e96a8 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/macos/Classes/messages.g.swift @@ -0,0 +1 @@ +../../darwin/Classes/messages.g.swift \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_foundation/macos/README.md b/packages/shared_preferences/shared_preferences_foundation/macos/README.md new file mode 100644 index 000000000000..fd7261950f35 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/macos/README.md @@ -0,0 +1,4 @@ +This only contains symlinks to ../darwin, to support versions of Flutter +prior that don't include https://github.com/flutter/flutter/pull/115337. +Once the minimum Flutter version supported by this implementation is one that +includes that functionality, this directory should be removed. diff --git a/packages/shared_preferences/shared_preferences_foundation/macos/shared_preferences_foundation.podspec b/packages/shared_preferences/shared_preferences_foundation/macos/shared_preferences_foundation.podspec new file mode 120000 index 000000000000..59dcc19e0bf9 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_foundation/macos/shared_preferences_foundation.podspec @@ -0,0 +1 @@ +../darwin/shared_preferences_foundation.podspec \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_ios/pigeons/copyright_header.txt b/packages/shared_preferences/shared_preferences_foundation/pigeons/copyright_header.txt similarity index 100% rename from packages/shared_preferences/shared_preferences_ios/pigeons/copyright_header.txt rename to packages/shared_preferences/shared_preferences_foundation/pigeons/copyright_header.txt diff --git a/packages/shared_preferences/shared_preferences_ios/pigeons/messages.dart b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart similarity index 63% rename from packages/shared_preferences/shared_preferences_ios/pigeons/messages.dart rename to packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart index 6b5648f9e2f0..81848ed53279 100644 --- a/packages/shared_preferences/shared_preferences_ios/pigeons/messages.dart +++ b/packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart @@ -6,17 +6,20 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/messages.g.dart', - dartTestOut: 'test/messages.g.dart', - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + dartTestOut: 'test/test_api.g.dart', + swiftOut: 'darwin/Classes/messages.g.swift', copyrightHeader: 'pigeons/copyright_header.txt', )) @HostApi(dartHostTestHandler: 'TestUserDefaultsApi') abstract class UserDefaultsApi { void remove(String key); + // TODO(stuartmorgan): Give these setters better Swift signatures (_,forKey:) + // once https://github.com/flutter/flutter/issues/105932 is fixed. void setBool(String key, bool value); void setDouble(String key, double value); void setValue(String key, Object value); + // TODO(stuartmorgan): Make these non-nullable once + // https://github.com/flutter/flutter/issues/97848 is fixed. Map getAll(); void clear(); } diff --git a/packages/shared_preferences/shared_preferences_macos/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml similarity index 52% rename from packages/shared_preferences/shared_preferences_macos/pubspec.yaml rename to packages/shared_preferences/shared_preferences_foundation/pubspec.yaml index 77f5f11f0525..3deb07fc5960 100644 --- a/packages/shared_preferences/shared_preferences_macos/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml @@ -1,20 +1,25 @@ -name: shared_preferences_macos -description: macOS implementation of the shared_preferences plugin. -repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_macos +name: shared_preferences_foundation +description: iOS and macOS implementation of the shared_preferences plugin. +repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.0.4 +version: 2.1.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: implements: shared_preferences platforms: + ios: + pluginClass: SharedPreferencesPlugin + dartPluginClass: SharedPreferencesFoundation + sharedDarwinSource: true macos: pluginClass: SharedPreferencesPlugin - dartPluginClass: SharedPreferencesMacOS + dartPluginClass: SharedPreferencesFoundation + sharedDarwinSource: true dependencies: flutter: @@ -24,3 +29,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^5.0.0 diff --git a/packages/shared_preferences/shared_preferences_ios/test/shared_preferences_ios_test.dart b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart similarity index 65% rename from packages/shared_preferences/shared_preferences_ios/test/shared_preferences_ios_test.dart rename to packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart index efafb230d9de..6c0635a3342f 100644 --- a/packages/shared_preferences/shared_preferences_ios/test/shared_preferences_ios_test.dart +++ b/packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart @@ -4,10 +4,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences_ios/shared_preferences_ios.dart'; +import 'package:shared_preferences_foundation/shared_preferences_foundation.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; -import 'messages.g.dart'; +import 'test_api.g.dart'; class _MockSharedPreferencesApi implements TestUserDefaultsApi { final Map items = {}; @@ -45,43 +45,52 @@ class _MockSharedPreferencesApi implements TestUserDefaultsApi { void main() { TestWidgetsFlutterBinding.ensureInitialized(); - _MockSharedPreferencesApi api = _MockSharedPreferencesApi(); - SharedPreferencesIOS plugin = SharedPreferencesIOS(); + late _MockSharedPreferencesApi api; setUp(() { api = _MockSharedPreferencesApi(); TestUserDefaultsApi.setup(api); - plugin = SharedPreferencesIOS(); }); test('registerWith', () { - SharedPreferencesIOS.registerWith(); - expect( - SharedPreferencesStorePlatform.instance, isA()); + SharedPreferencesFoundation.registerWith(); + expect(SharedPreferencesStorePlatform.instance, + isA()); }); test('remove', () async { + final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); api.items['flutter.hi'] = 'world'; expect(await plugin.remove('flutter.hi'), isTrue); expect(api.items.containsKey('flutter.hi'), isFalse); }); test('clear', () async { + final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); api.items['flutter.hi'] = 'world'; expect(await plugin.clear(), isTrue); expect(api.items.containsKey('flutter.hi'), isFalse); }); test('getAll', () async { - api.items['flutter.hi'] = 'world'; - api.items['flutter.bye'] = 'dust'; + final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); + api.items['flutter.aBool'] = true; + api.items['flutter.aDouble'] = 3.14; + api.items['flutter.anInt'] = 42; + api.items['flutter.aString'] = 'hello world'; + api.items['flutter.aStringList'] = ['hello', 'world']; final Map all = await plugin.getAll(); - expect(all.length, 2); - expect(all['flutter.hi'], api.items['flutter.hi']); - expect(all['flutter.bye'], api.items['flutter.bye']); + expect(all.length, 5); + expect(all['flutter.aBool'], api.items['flutter.aBool']); + expect(all['flutter.aDouble'], + closeTo(api.items['flutter.aDouble']! as num, 0.0001)); + expect(all['flutter.anInt'], api.items['flutter.anInt']); + expect(all['flutter.aString'], api.items['flutter.aString']); + expect(all['flutter.aStringList'], api.items['flutter.aStringList']); }); test('setValue', () async { + final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); expect(await plugin.setValue('Bool', 'flutter.Bool', true), isTrue); expect(api.items['flutter.Bool'], true); expect(await plugin.setValue('Double', 'flutter.Double', 1.5), isTrue); @@ -98,6 +107,7 @@ void main() { }); test('setValue with unsupported type', () { + final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); expect(() async { await plugin.setValue('Map', 'flutter.key', {}); }, throwsA(isA())); diff --git a/packages/shared_preferences/shared_preferences_ios/test/messages.g.dart b/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart similarity index 88% rename from packages/shared_preferences/shared_preferences_ios/test/messages.g.dart rename to packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart index 12fbc0635784..12f97bd2b794 100644 --- a/packages/shared_preferences/shared_preferences_ios/test/messages.g.dart +++ b/packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart @@ -1,31 +1,33 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v1.0.16), do not edit directly. +// Autogenerated from Pigeon (v5.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import -// @dart = 2.12 +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import +// ignore_for_file: avoid_relative_lib_imports import 'dart:async'; -import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; -import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences_ios/messages.g.dart'; - -class _TestUserDefaultsApiCodec extends StandardMessageCodec { - const _TestUserDefaultsApiCodec(); -} +import 'package:shared_preferences_foundation/messages.g.dart'; abstract class TestUserDefaultsApi { - static const MessageCodec codec = _TestUserDefaultsApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void remove(String key); + void setBool(String key, bool value); + void setDouble(String key, double value); + void setValue(String key, Object value); + Map getAll(); + void clear(); + static void setup(TestUserDefaultsApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -43,7 +45,7 @@ abstract class TestUserDefaultsApi { assert(arg_key != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.remove was null, expected non-null String.'); api.remove(arg_key!); - return {}; + return []; }); } } @@ -65,7 +67,7 @@ abstract class TestUserDefaultsApi { assert(arg_value != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null, expected non-null bool.'); api.setBool(arg_key!, arg_value!); - return {}; + return []; }); } } @@ -87,7 +89,7 @@ abstract class TestUserDefaultsApi { assert(arg_value != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null, expected non-null double.'); api.setDouble(arg_key!, arg_value!); - return {}; + return []; }); } } @@ -109,7 +111,7 @@ abstract class TestUserDefaultsApi { assert(arg_value != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null, expected non-null Object.'); api.setValue(arg_key!, arg_value!); - return {}; + return []; }); } } @@ -123,7 +125,7 @@ abstract class TestUserDefaultsApi { channel.setMockMessageHandler((Object? message) async { // ignore message final Map output = api.getAll(); - return {'result': output}; + return [output]; }); } } @@ -137,7 +139,7 @@ abstract class TestUserDefaultsApi { channel.setMockMessageHandler((Object? message) async { // ignore message api.clear(); - return {}; + return []; }); } } diff --git a/packages/shared_preferences/shared_preferences_ios/AUTHORS b/packages/shared_preferences/shared_preferences_ios/AUTHORS deleted file mode 100644 index 493a0b4ef9c2..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/AUTHORS +++ /dev/null @@ -1,66 +0,0 @@ -# Below is a list of people and organizations that have contributed -# to the Flutter project. Names should be added to the list like so: -# -# Name/Organization - -Google Inc. -The Chromium Authors -German Saprykin -Benjamin Sauer -larsenthomasj@gmail.com -Ali Bitek -Pol Batlló -Anatoly Pulyaevskiy -Hayden Flinner -Stefano Rodriguez -Salvatore Giordano -Brian Armstrong -Paul DeMarco -Fabricio Nogueira -Simon Lightfoot -Ashton Thomas -Thomas Danner -Diego Velásquez -Hajime Nakamura -Tuyển Vũ Xuân -Miguel Ruivo -Sarthak Verma -Mike Diarmid -Invertase -Elliot Hesp -Vince Varga -Aawaz Gyawali -EUI Limited -Katarina Sheremet -Thomas Stockx -Sarbagya Dhaubanjar -Ozkan Eksi -Rishab Nayak -ko2ic -Jonathan Younger -Jose Sanchez -Debkanchan Samadder -Audrius Karosevicius -Lukasz Piliszczuk -SoundReply Solutions GmbH -Rafal Wachol -Pau Picas -Christian Weder -Alexandru Tuca -Christian Weder -Rhodes Davis Jr. -Luigi Agosti -Quentin Le Guennec -Koushik Ravikumar -Nissim Dsilva -Giancarlo Rocha -Ryo Miyake -Théo Champion -Kazuki Yamaguchi -Eitan Schwartz -Chris Rutkowski -Juan Alvarez -Aleksandr Yurkovskiy -Anton Borries -Alex Li -Rahul Raj <64.rahulraj@gmail.com> diff --git a/packages/shared_preferences/shared_preferences_ios/CHANGELOG.md b/packages/shared_preferences/shared_preferences_ios/CHANGELOG.md deleted file mode 100644 index d2101e0784cf..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -## NEXT - -* Updates code for `no_leading_underscores_for_local_identifiers` lint. -* Updates minimum Flutter version to 2.10. - -## 2.1.1 - -* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors - lint warnings. - -## 2.1.0 - -* Upgrades to using Pigeon. - -## 2.0.10 - -* Switches to an in-package method channel implementation. - -## 2.0.9 - -* Removes dependency on `meta`. - -## 2.0.8 - -* Split from `shared_preferences` as a federated implementation. diff --git a/packages/shared_preferences/shared_preferences_ios/LICENSE b/packages/shared_preferences/shared_preferences_ios/LICENSE deleted file mode 100644 index c6823b81eb84..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/shared_preferences/shared_preferences_ios/example/.metadata b/packages/shared_preferences/shared_preferences_ios/example/.metadata deleted file mode 100644 index e0e9530fccc9..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 79b49b9e1057f90ebf797725233c6b311722de69 - channel: dev - -project_type: app diff --git a/packages/shared_preferences/shared_preferences_ios/example/README.md b/packages/shared_preferences/shared_preferences_ios/example/README.md deleted file mode 100644 index 96b8bb17dbff..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Platform Implementation Test App - -This is a test app for manual testing and automated integration testing -of this platform implementation. It is not intended to demonstrate actual use of -this package, since the intent is that plugin clients use the app-facing -package. - -Unless you are making changes to this implementation package, this example is -very unlikely to be relevant. diff --git a/packages/shared_preferences/shared_preferences_ios/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_ios/example/integration_test/shared_preferences_test.dart deleted file mode 100644 index b4b21871701c..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/integration_test/shared_preferences_test.dart +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - group('SharedPreferencesIos', () { - const Map kTestValues = { - 'flutter.String': 'hello world', - 'flutter.bool': true, - 'flutter.int': 42, - 'flutter.double': 3.14159, - 'flutter.List': ['foo', 'bar'], - }; - - const Map kTestValues2 = { - 'flutter.String': 'goodbye world', - 'flutter.bool': false, - 'flutter.int': 1337, - 'flutter.double': 2.71828, - 'flutter.List': ['baz', 'quox'], - }; - - late SharedPreferencesStorePlatform preferences; - - setUp(() async { - preferences = SharedPreferencesStorePlatform.instance; - }); - - tearDown(() { - preferences.clear(); - }); - - // Normally the app-facing package adds the prefix, but since this test - // bypasses the app-facing package it needs to be manually added. - String prefixedKey(String key) { - return 'flutter.$key'; - } - - testWidgets('reading', (WidgetTester _) async { - final Map values = await preferences.getAll(); - expect(values[prefixedKey('String')], isNull); - expect(values[prefixedKey('bool')], isNull); - expect(values[prefixedKey('int')], isNull); - expect(values[prefixedKey('double')], isNull); - expect(values[prefixedKey('List')], isNull); - }); - - testWidgets('writing', (WidgetTester _) async { - await Future.wait(>[ - preferences.setValue( - 'String', prefixedKey('String'), kTestValues2['flutter.String']!), - preferences.setValue( - 'Bool', prefixedKey('bool'), kTestValues2['flutter.bool']!), - preferences.setValue( - 'Int', prefixedKey('int'), kTestValues2['flutter.int']!), - preferences.setValue( - 'Double', prefixedKey('double'), kTestValues2['flutter.double']!), - preferences.setValue( - 'StringList', prefixedKey('List'), kTestValues2['flutter.List']!) - ]); - final Map values = await preferences.getAll(); - expect(values[prefixedKey('String')], kTestValues2['flutter.String']); - expect(values[prefixedKey('bool')], kTestValues2['flutter.bool']); - expect(values[prefixedKey('int')], kTestValues2['flutter.int']); - expect(values[prefixedKey('double')], kTestValues2['flutter.double']); - expect(values[prefixedKey('List')], kTestValues2['flutter.List']); - }); - - testWidgets('removing', (WidgetTester _) async { - final String key = prefixedKey('testKey'); - await preferences.setValue('String', key, kTestValues['flutter.String']!); - await preferences.setValue('Bool', key, kTestValues['flutter.bool']!); - await preferences.setValue('Int', key, kTestValues['flutter.int']!); - await preferences.setValue('Double', key, kTestValues['flutter.double']!); - await preferences.setValue( - 'StringList', key, kTestValues['flutter.List']!); - await preferences.remove(key); - final Map values = await preferences.getAll(); - expect(values[key], isNull); - }); - - testWidgets('clearing', (WidgetTester _) async { - await preferences.setValue( - 'String', 'String', kTestValues['flutter.String']!); - await preferences.setValue('Bool', 'bool', kTestValues['flutter.bool']!); - await preferences.setValue('Int', 'int', kTestValues['flutter.int']!); - await preferences.setValue( - 'Double', 'double', kTestValues['flutter.double']!); - await preferences.setValue( - 'StringList', 'List', kTestValues['flutter.List']!); - await preferences.clear(); - final Map values = await preferences.getAll(); - expect(values['String'], null); - expect(values['bool'], null); - expect(values['int'], null); - expect(values['double'], null); - expect(values['List'], null); - }); - }); -} diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.h b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.h deleted file mode 100644 index 0681d288bb70..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.m b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.m deleted file mode 100644 index b790a0a52635..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -@end diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf03016f6c994b70f38d1b7346e5831b531f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 564 zcmV-40?Yl0P)Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0bca859a3f474b03065bef75ba58a9e4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7edb86cdfe0d15b4b0d98334a86163658..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1716 zcmds$`#;kQ7{|XelZftyR5~xW7?MLxS4^|Hw3&P7^y)@A9Fj{Xm1~_CIV^XZ%SLBn zA;!r`GqGHg=7>xrB{?psZQs88ZaedDoagm^KF{a*>G|dJWRSe^I$DNW008I^+;Kjt z>9p3GNR^I;v>5_`+91i(*G;u5|L+Bu6M=(afLjtkya#yZ175|z$pU~>2#^Z_pCZ7o z1c6UNcv2B3?; zX%qdxCXQpdKRz=#b*q0P%b&o)5ZrNZt7$fiETSK_VaY=mb4GK`#~0K#~9^ zcY!`#Af+4h?UMR-gMKOmpuYeN5P*RKF!(tb`)oe0j2BH1l?=>y#S5pMqkx6i{*=V9JF%>N8`ewGhRE(|WohnD59R^$_36{4>S zDFlPC5|k?;SPsDo87!B{6*7eqmMdU|QZ84>6)Kd9wNfh90=y=TFQay-0__>=<4pk& zYDjgIhL-jQ9o>z32K)BgAH+HxamL{ZL~ozu)Qqe@a`FpH=oQRA8=L-m-1dam(Ix2V z?du;LdMO+ooBelr^_y4{|44tmgH^2hSzPFd;U^!1p>6d|o)(-01z{i&Kj@)z-yfWQ)V#3Uo!_U}q3u`(fOs`_f^ueFii1xBNUB z6MecwJN$CqV&vhc+)b(p4NzGGEgwWNs z@*lUV6LaduZH)4_g!cE<2G6#+hJrWd5(|p1Z;YJ7ifVHv+n49btR}dq?HHDjl{m$T z!jLZcGkb&XS2OG~u%&R$(X+Z`CWec%QKt>NGYvd5g20)PU(dOn^7%@6kQb}C(%=vr z{?RP(z~C9DPnL{q^@pVw@|Vx~@3v!9dCaBtbh2EdtoNHm4kGxp>i#ct)7p|$QJs+U z-a3qtcPvhihub?wnJqEt>zC@)2suY?%-96cYCm$Q8R%-8$PZYsx3~QOLMDf(piXMm zB=<63yQk1AdOz#-qsEDX>>c)EES%$owHKue;?B3)8aRd}m~_)>SL3h2(9X;|+2#7X z+#2)NpD%qJvCQ0a-uzZLmz*ms+l*N}w)3LRQ*6>|Ub-fyptY(keUxw+)jfwF5K{L9 z|Cl_w=`!l_o><384d&?)$6Nh(GAm=4p_;{qVn#hI8lqewW7~wUlyBM-4Z|)cZr?Rh z=xZ&Ol>4(CU85ea(CZ^aO@2N18K>ftl8>2MqetAR53_JA>Fal`^)1Y--Am~UDa4th zKfCYpcXky$XSFDWBMIl(q=Mxj$iMBX=|j9P)^fDmF(5(5$|?Cx}DKEJa&XZP%OyE`*GvvYQ4PV&!g2|L^Q z?YG}tx;sY@GzMmsY`7r$P+F_YLz)(e}% zyakqFB<6|x9R#TdoP{R$>o7y(-`$$p0NxJ6?2B8tH)4^yF(WhqGZlM3=9Ibs$%U1w zWzcss*_c0=v_+^bfb`kBFsI`d;ElwiU%frgRB%qBjn@!0U2zZehBn|{%uNIKBA7n= zzE`nnwTP85{g;8AkYxA68>#muXa!G>xH22D1I*SiD~7C?7Za+9y7j1SHiuSkKK*^O zsZ==KO(Ua#?YUpXl{ViynyT#Hzk=}5X$e04O@fsMQjb}EMuPWFO0e&8(2N(29$@Vd zn1h8Yd>6z(*p^E{c(L0Lg=wVdupg!z@WG;E0k|4a%s7Up5C0c)55XVK*|x9RQeZ1J@1v9MX;>n34(i>=YE@Iur`0Vah(inE3VUFZNqf~tSz{1fz3Fsn_x4F>o(Yo;kpqvBe-sbwH(*Y zu$JOl0b83zu$JMvy<#oH^Wl>aWL*?aDwnS0iEAwC?DK@aT)GHRLhnz2WCvf3Ba;o=aY7 z2{Asu5MEjGOY4O#Ggz@@J;q*0`kd2n8I3BeNuMmYZf{}pg=jTdTCrIIYuW~luKecn z+E-pHY%ohj@uS0%^ z&(OxwPFPD$+#~`H?fMvi9geVLci(`K?Kj|w{rZ9JgthFHV+=6vMbK~0)Ea<&WY-NC zy-PnZft_k2tfeQ*SuC=nUj4H%SQ&Y$gbH4#2sT0cU0SdFs=*W*4hKGpuR1{)mV;Qf5pw4? zfiQgy0w3fC*w&Bj#{&=7033qFR*<*61B4f9K%CQvxEn&bsWJ{&winp;FP!KBj=(P6 z4Z_n4L7cS;ao2)ax?Tm|I1pH|uLpDSRVghkA_UtFFuZ0b2#>!8;>-_0ELjQSD-DRd z4im;599VHDZYtnWZGAB25W-e(2VrzEh|etsv2YoP#VbIZ{aFkwPrzJ#JvCvA*mXS& z`}Q^v9(W4GiSs}#s7BaN!WA2bniM$0J(#;MR>uIJ^uvgD3GS^%*ikdW6-!VFUU?JV zZc2)4cMsX@j z5HQ^e3BUzOdm}yC-xA%SY``k$rbfk z;CHqifhU*jfGM@DkYCecD9vl*qr58l6x<8URB=&%{!Cu3RO*MrKZ4VO}V6R0a zZw3Eg^0iKWM1dcTYZ0>N899=r6?+adUiBKPciJw}L$=1f4cs^bio&cr9baLF>6#BM z(F}EXe-`F=f_@`A7+Q&|QaZ??Txp_dB#lg!NH=t3$G8&06MFhwR=Iu*Im0s_b2B@| znW>X}sy~m#EW)&6E&!*0%}8UAS)wjt+A(io#wGI@Z2S+Ms1Cxl%YVE800007ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5cee1c98386d13b17e89f719e83555b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609df07bf62e5100a53a01510388bd2b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609df07bf62e5100a53a01510388bd2b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a5a98e212cca15ea7bf2ab5de5108680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3831 zcmVjJBgitF5mAp-i>4+KS_oR{|13AP->1TD4=w)g|)JHOx|a2Wk1Va z!k)vP$UcQ#mdj%wNQoaJ!w>jv_6&JPyutpQps?s5dmDQ>`%?Bvj>o<%kYG!YW6H-z zu`g$@mp`;qDR!51QaS}|ZToSuAGcJ7$2HF0z`ln4t!#Yg46>;vGG9N9{V@9z#}6v* zfP?}r6b{*-C*)(S>NECI_E~{QYzN5SXRmVnP<=gzP+_Sp(Aza_hKlZ{C1D&l*(7IKXxQC1Z9#6wx}YrGcn~g%;icdw>T0Rf^w0{ z$_wn1J+C0@!jCV<%Go5LA45e{5gY9PvZp8uM$=1}XDI+9m7!A95L>q>>oe0$nC->i zeexUIvq%Uk<-$>DiDb?!In)lAmtuMWxvWlk`2>4lNuhSsjAf2*2tjT`y;@d}($o)S zn(+W&hJ1p0xy@oxP%AM15->wPLp{H!k)BdBD$toBpJh+crWdsNV)qsHaqLg2_s|Ih z`8E9z{E3sA!}5aKu?T!#enD(wLw?IT?k-yWVHZ8Akz4k5(TZJN^zZgm&zM28sfTD2BYJ|Fde3Xzh;;S` z=GXTnY4Xc)8nYoz6&vF;P7{xRF-{|2Xs5>a5)@BrnQ}I(_x7Cgpx#5&Td^4Q9_FnQ zX5so*;#8-J8#c$OlA&JyPp$LKUhC~-e~Ij!L%uSMu!-VZG7Hx-L{m2DVR2i=GR(_% zCVD!4N`I)&Q5S`?P&fQZ=4#Dgt_v2-DzkT}K(9gF0L(owe-Id$Rc2qZVLqI_M_DyO z9@LC#U28_LU{;wGZ&))}0R2P4MhajKCd^K#D+JJ&JIXZ_p#@+7J9A&P<0kdRujtQ_ zOy>3=C$kgi6$0pW06KaLz!21oOryKM3ZUOWqppndxfH}QpgjEJ`j7Tzn5bk6K&@RA?vl##y z$?V~1E(!wB5rH`>3nc&@)|#<1dN2cMzzm=PGhQ|Yppne(C-Vlt450IXc`J4R0W@I7 zd1e5uW6juvO%ni(WX7BsKx3MLngO7rHO;^R5I~0^nE^9^E_eYLgiR9&KnJ)pBbfno zSVnW$0R+&6jOOsZ82}nJ126+c|%svPo;TeUku<2G7%?$oft zyaO;tVo}(W)VsTUhq^XmFi#2z%-W9a{7mXn{uzivYQ_d6b7VJG{77naW(vHt-uhnY zVN#d!JTqVh(7r-lhtXVU6o})aZbDt_;&wJVGl2FKYFBFpU-#9U)z#(A%=IVnqytR$SY-sO( z($oNE09{D^@OuYPz&w~?9>Fl5`g9u&ecFGhqX=^#fmR=we0CJw+5xna*@oHnkahk+ z9aWeE3v|An+O5%?4fA&$Fgu~H_YmqR!yIU!bFCk4!#pAj%(lI(A5n)n@Id#M)O9Yx zJU9oKy{sRAIV3=5>(s8n{8ryJ!;ho}%pn6hZKTKbqk=&m=f*UnK$zW3YQP*)pw$O* zIfLA^!-bmBl6%d_n$#tP8Zd_(XdA*z*WH|E_yILwjtI~;jK#v-6jMl^?<%Y%`gvpwv&cFb$||^v4D&V=aNy?NGo620jL3VZnA%s zH~I|qPzB~e(;p;b^gJr7Ure#7?8%F0m4vzzPy^^(q4q1OdthF}Fi*RmVZN1OwTsAP zn9CZP`FazX3^kG(KodIZ=Kty8DLTy--UKfa1$6XugS zk%6v$Kmxt6U!YMx0JQ)0qX*{CXwZZk$vEROidEc7=J-1;peNat!vS<3P-FT5po>iE z!l3R+<`#x|+_hw!HjQGV=8!q|76y8L7N8gP3$%0kfush|u0uU^?dKBaeRSBUpOZ0c z62;D&Mdn2}N}xHRFTRI?zRv=>=AjHgH}`2k4WK=#AHB)UFrR-J87GgX*x5fL^W2#d z=(%K8-oZfMO=i{aWRDg=FX}UubM4eotRDcn;OR#{3q=*?3mE3_oJ-~prjhxh%PgQT zyn)Qozaq0@o&|LEgS{Ind4Swsr;b`u185hZPOBLL<`d2%^Yp1?oL)=jnLi;Zo0ZDliTtQ^b5SmfIMe{T==zZkbvn$KTQGlbG8w}s@M3TZnde;1Am46P3juKb zl9GU&3F=q`>j!`?SyH#r@O59%@aMX^rx}Nxe<>NqpUp5=lX1ojGDIR*-D^SDuvCKF z?3$xG(gVUsBERef_YjPFl^rU9EtD{pt z0CXwpN7BN3!8>hajGaTVk-wl=9rxmfWtIhC{mheHgStLi^+Nz12a?4r(fz)?3A%at zMlvQmL<2-R)-@G1wJ0^zQK%mR=r4d{Y3fHp){nWXUL#|CqXl(+v+qDh>FkF9`eWrW zfr^D%LNfOcTNvtx0JXR35J0~Jpi2#P3Q&80w+nqNfc}&G0A~*)lGHKv=^FE+b(37|)zL;KLF>oiGfb(?&1 zV3XRu!Sw>@quKiab%g6jun#oZ%!>V#A%+lNc?q>6+VvyAn=kf_6z^(TZUa4Eelh{{ zqFX-#dY(EV@7l$NE&kv9u9BR8&Ojd#ZGJ6l8_BW}^r?DIS_rU2(XaGOK z225E@kH5Opf+CgD^{y29jD4gHbGf{1MD6ggQ&%>UG4WyPh5q_tb`{@_34B?xfSO*| zZv8!)q;^o-bz`MuxXk*G^}(6)ACb@=Lfs`Hxoh>`Y0NE8QRQ!*p|SH@{r8=%RKd4p z+#Ty^-0kb=-H-O`nAA3_6>2z(D=~Tbs(n8LHxD0`R0_ATFqp-SdY3(bZ3;VUM?J=O zKCNsxsgt@|&nKMC=*+ZqmLHhX1KHbAJs{nGVMs6~TiF%Q)P@>!koa$%oS zjXa=!5>P`vC-a}ln!uH1ooeI&v?=?v7?1n~P(wZ~0>xWxd_Aw;+}9#eULM7M8&E?Y zC-ZLhi3RoM92SXUb-5i-Lmt5_rfjE{6y^+24`y$1lywLyHO!)Boa7438K4#iLe?rh z2O~YGSgFUBH?og*6=r9rme=peP~ah`(8Zt7V)j5!V0KPFf_mebo3z95U8(up$-+EA^9dTRLq>Yl)YMBuch9%=e5B`Vnb>o zt03=kq;k2TgGe4|lGne&zJa~h(UGutjP_zr?a7~#b)@15XNA>Dj(m=gg2Q5V4-$)D|Q9}R#002ovPDHLkV1o7DH3k3x diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/shared_preferences/shared_preferences_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39da7941ef3f6dcb7f06a192d8dcb308d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8 -#import -#import "AppDelegate.h" - -int main(int argc, char *argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/Info.plist b/packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/Info.plist deleted file mode 100644 index 64d65ca49577..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/SharedPreferencesTests.m b/packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/SharedPreferencesTests.m deleted file mode 100644 index 792f6c111b82..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/ios/RunnerTests/SharedPreferencesTests.m +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import shared_preferences_ios; -@import XCTest; - -@interface SharedPreferencesTests : XCTestCase -@end - -@implementation SharedPreferencesTests - -- (void)testPlugin { - FLTSharedPreferencesPlugin *plugin = [[FLTSharedPreferencesPlugin alloc] init]; - XCTAssertNotNil(plugin); -} - -@end diff --git a/packages/shared_preferences/shared_preferences_ios/example/lib/main.dart b/packages/shared_preferences/shared_preferences_ios/example/lib/main.dart deleted file mode 100644 index bb513b09f6d5..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/lib/main.dart +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: public_member_api_docs - -import 'package:flutter/material.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - title: 'SharedPreferences Demo', - home: SharedPreferencesDemo(), - ); - } -} - -class SharedPreferencesDemo extends StatefulWidget { - const SharedPreferencesDemo({Key? key}) : super(key: key); - - @override - SharedPreferencesDemoState createState() => SharedPreferencesDemoState(); -} - -class SharedPreferencesDemoState extends State { - final SharedPreferencesStorePlatform _prefs = - SharedPreferencesStorePlatform.instance; - late Future _counter; - - // Includes the prefix because this is using the platform interface directly, - // but the prefix (which the native code assumes is present) is added by the - // app-facing package. - static const String _prefKey = 'flutter.counter'; - - Future _incrementCounter() async { - final Map values = await _prefs.getAll(); - final int counter = ((values[_prefKey] as int?) ?? 0) + 1; - - setState(() { - _counter = _prefs.setValue('Int', _prefKey, counter).then((bool success) { - return counter; - }); - }); - } - - @override - void initState() { - super.initState(); - _counter = _prefs.getAll().then((Map values) { - return (values[_prefKey] as int?) ?? 0; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('SharedPreferences Demo'), - ), - body: Center( - child: FutureBuilder( - future: _counter, - builder: (BuildContext context, AsyncSnapshot snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.waiting: - return const CircularProgressIndicator(); - default: - if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else { - return Text( - 'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n' - 'This should persist across restarts.', - ); - } - } - })), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } -} diff --git a/packages/shared_preferences/shared_preferences_ios/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_ios/example/pubspec.yaml deleted file mode 100644 index 446cea1e0508..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/pubspec.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: shared_preferences_example -description: Demonstrates how to use the shared_preferences plugin. -publish_to: none - -environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" - -dependencies: - flutter: - sdk: flutter - shared_preferences_ios: - # When depending on this package from a real application you should use: - # shared_preferences_ios: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - shared_preferences_platform_interface: ^2.0.0 - -dev_dependencies: - flutter_driver: - sdk: flutter - flutter_test: - sdk: flutter - integration_test: - sdk: flutter - -flutter: - uses-material-design: true diff --git a/packages/shared_preferences/shared_preferences_ios/example/test_driver/integration_test.dart b/packages/shared_preferences/shared_preferences_ios/example/test_driver/integration_test.dart deleted file mode 100644 index 4f10f2a522f3..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/example/test_driver/integration_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:integration_test/integration_test_driver.dart'; - -Future main() => integrationDriver(); diff --git a/packages/shared_preferences/shared_preferences_ios/ios/Assets/.gitkeep b/packages/shared_preferences/shared_preferences_ios/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.h b/packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.h deleted file mode 100644 index d2d04aee3b64..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -@interface FLTSharedPreferencesPlugin : NSObject -@end diff --git a/packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.m b/packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.m deleted file mode 100644 index bb11da2b5406..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/ios/Classes/FLTSharedPreferencesPlugin.m +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FLTSharedPreferencesPlugin.h" -#import "messages.g.h" - -static NSMutableDictionary *getAllPrefs() { - NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier]; - NSDictionary *prefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:appDomain]; - NSMutableDictionary *filteredPrefs = [NSMutableDictionary dictionary]; - if (prefs != nil) { - for (NSString *candidateKey in prefs) { - if ([candidateKey hasPrefix:@"flutter."]) { - [filteredPrefs setObject:prefs[candidateKey] forKey:candidateKey]; - } - } - } - return filteredPrefs; -} - -@interface FLTSharedPreferencesPlugin () -@end - -@implementation FLTSharedPreferencesPlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FLTSharedPreferencesPlugin *plugin = [[FLTSharedPreferencesPlugin alloc] init]; - UserDefaultsApiSetup(registrar.messenger, plugin); -} - -// Must not return nil unless "error" is set. -- (nullable NSDictionary *)getAllWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return getAllPrefs(); -} - -- (void)clearWithError:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - for (NSString *key in getAllPrefs()) { - [defaults removeObjectForKey:key]; - } -} - -- (void)removeKey:(nonnull NSString *)key - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; -} - -- (void)setBoolKey:(nonnull NSString *)key - value:(nonnull NSNumber *)value - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - [[NSUserDefaults standardUserDefaults] setBool:value.boolValue forKey:key]; -} - -- (void)setDoubleKey:(nonnull NSString *)key - value:(nonnull NSNumber *)value - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - [[NSUserDefaults standardUserDefaults] setDouble:value.doubleValue forKey:key]; -} - -- (void)setValueKey:(nonnull NSString *)key - value:(nonnull NSString *)value - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]; -} - -@end diff --git a/packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.h b/packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.h deleted file mode 100644 index 592402344a04..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v1.0.16), do not edit directly. -// See also: https://pub.dev/packages/pigeon -#import -@protocol FlutterBinaryMessenger; -@protocol FlutterMessageCodec; -@class FlutterError; -@class FlutterStandardTypedData; - -NS_ASSUME_NONNULL_BEGIN - -/// The codec used by UserDefaultsApi. -NSObject *UserDefaultsApiGetCodec(void); - -@protocol UserDefaultsApi -- (void)removeKey:(NSString *)key error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setBoolKey:(NSString *)key - value:(NSNumber *)value - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setDoubleKey:(NSString *)key - value:(NSNumber *)value - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setValueKey:(NSString *)key value:(id)value error:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSDictionary *)getAllWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)clearWithError:(FlutterError *_Nullable *_Nonnull)error; -@end - -extern void UserDefaultsApiSetup(id binaryMessenger, - NSObject *_Nullable api); - -NS_ASSUME_NONNULL_END diff --git a/packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.m b/packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.m deleted file mode 100644 index ea8c45c7a66c..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/ios/Classes/messages.g.m +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v1.0.16), do not edit directly. -// See also: https://pub.dev/packages/pigeon -#import "messages.g.h" -#import - -#if !__has_feature(objc_arc) -#error File requires ARC to be enabled. -#endif - -static NSDictionary *wrapResult(id result, FlutterError *error) { - NSDictionary *errorDict = (NSDictionary *)[NSNull null]; - if (error) { - errorDict = @{ - @"code" : (error.code ? error.code : [NSNull null]), - @"message" : (error.message ? error.message : [NSNull null]), - @"details" : (error.details ? error.details : [NSNull null]), - }; - } - return @{ - @"result" : (result ? result : [NSNull null]), - @"error" : errorDict, - }; -} - -@interface UserDefaultsApiCodecReader : FlutterStandardReader -@end -@implementation UserDefaultsApiCodecReader -@end - -@interface UserDefaultsApiCodecWriter : FlutterStandardWriter -@end -@implementation UserDefaultsApiCodecWriter -@end - -@interface UserDefaultsApiCodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation UserDefaultsApiCodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[UserDefaultsApiCodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[UserDefaultsApiCodecReader alloc] initWithData:data]; -} -@end - -NSObject *UserDefaultsApiGetCodec() { - static dispatch_once_t s_pred = 0; - static FlutterStandardMessageCodec *s_sharedObject = nil; - dispatch_once(&s_pred, ^{ - UserDefaultsApiCodecReaderWriter *readerWriter = - [[UserDefaultsApiCodecReaderWriter alloc] init]; - s_sharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return s_sharedObject; -} - -void UserDefaultsApiSetup(id binaryMessenger, - NSObject *api) { - { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.remove" - binaryMessenger:binaryMessenger - codec:UserDefaultsApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(removeKey:error:)], - @"UserDefaultsApi api (%@) doesn't respond to @selector(removeKey:error:)", api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_key = args[0]; - FlutterError *error; - [api removeKey:arg_key error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.setBool" - binaryMessenger:binaryMessenger - codec:UserDefaultsApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(setBoolKey:value:error:)], - @"UserDefaultsApi api (%@) doesn't respond to @selector(setBoolKey:value:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_key = args[0]; - NSNumber *arg_value = args[1]; - FlutterError *error; - [api setBoolKey:arg_key value:arg_value error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.setDouble" - binaryMessenger:binaryMessenger - codec:UserDefaultsApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(setDoubleKey:value:error:)], - @"UserDefaultsApi api (%@) doesn't respond to @selector(setDoubleKey:value:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_key = args[0]; - NSNumber *arg_value = args[1]; - FlutterError *error; - [api setDoubleKey:arg_key value:arg_value error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.setValue" - binaryMessenger:binaryMessenger - codec:UserDefaultsApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(setValueKey:value:error:)], - @"UserDefaultsApi api (%@) doesn't respond to @selector(setValueKey:value:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_key = args[0]; - id arg_value = args[1]; - FlutterError *error; - [api setValueKey:arg_key value:arg_value error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.getAll" - binaryMessenger:binaryMessenger - codec:UserDefaultsApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(getAllWithError:)], - @"UserDefaultsApi api (%@) doesn't respond to @selector(getAllWithError:)", api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSDictionary *output = [api getAllWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel - messageChannelWithName:@"dev.flutter.pigeon.UserDefaultsApi.clear" - binaryMessenger:binaryMessenger - codec:UserDefaultsApiGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(clearWithError:)], - @"UserDefaultsApi api (%@) doesn't respond to @selector(clearWithError:)", api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api clearWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } -} diff --git a/packages/shared_preferences/shared_preferences_ios/ios/shared_preferences_ios.podspec b/packages/shared_preferences/shared_preferences_ios/ios/shared_preferences_ios.podspec deleted file mode 100644 index 4126ed0a8cc2..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/ios/shared_preferences_ios.podspec +++ /dev/null @@ -1,23 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# -Pod::Spec.new do |s| - s.name = 'shared_preferences_ios' - s.version = '0.0.1' - s.summary = 'Flutter Shared Preferences' - s.description = <<-DESC -Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. - DESC - s.homepage = 'https://github.com/flutter/plugins' - s.license = { :type => 'BSD', :file => '../LICENSE' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_ios' } - s.documentation_url = 'https://pub.dev/packages/shared_preferences' - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' - s.dependency 'Flutter' - - s.platform = :ios, '9.0' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } -end - diff --git a/packages/shared_preferences/shared_preferences_ios/pubspec.yaml b/packages/shared_preferences/shared_preferences_ios/pubspec.yaml deleted file mode 100644 index 45b1aae2c473..000000000000 --- a/packages/shared_preferences/shared_preferences_ios/pubspec.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: shared_preferences_ios -description: iOS implementation of the shared_preferences plugin -repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_ios -issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.1 - -environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" - -flutter: - plugin: - implements: shared_preferences - platforms: - ios: - dartPluginClass: SharedPreferencesIOS - pluginClass: FLTSharedPreferencesPlugin - -dependencies: - flutter: - sdk: flutter - shared_preferences_platform_interface: ^2.0.0 - -dev_dependencies: - flutter_test: - sdk: flutter - pigeon: ^1.0.16 diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md index 7c518fcf71cb..3c5a398546d1 100644 --- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.1.3 + +* Updates code for stricter lint checks. + ## 2.1.2 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart index d51be33baeed..a904c824d4fe 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart @@ -66,9 +66,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml index 9418c0581ed7..98ff24a84682 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml index cd2ca8007e3c..21203a877586 100644 --- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -2,11 +2,11 @@ name: shared_preferences_linux description: Linux implementation of the shared_preferences plugin repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.2 +version: 2.1.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_macos/AUTHORS b/packages/shared_preferences/shared_preferences_macos/AUTHORS deleted file mode 100644 index 493a0b4ef9c2..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/AUTHORS +++ /dev/null @@ -1,66 +0,0 @@ -# Below is a list of people and organizations that have contributed -# to the Flutter project. Names should be added to the list like so: -# -# Name/Organization - -Google Inc. -The Chromium Authors -German Saprykin -Benjamin Sauer -larsenthomasj@gmail.com -Ali Bitek -Pol Batlló -Anatoly Pulyaevskiy -Hayden Flinner -Stefano Rodriguez -Salvatore Giordano -Brian Armstrong -Paul DeMarco -Fabricio Nogueira -Simon Lightfoot -Ashton Thomas -Thomas Danner -Diego Velásquez -Hajime Nakamura -Tuyển Vũ Xuân -Miguel Ruivo -Sarthak Verma -Mike Diarmid -Invertase -Elliot Hesp -Vince Varga -Aawaz Gyawali -EUI Limited -Katarina Sheremet -Thomas Stockx -Sarbagya Dhaubanjar -Ozkan Eksi -Rishab Nayak -ko2ic -Jonathan Younger -Jose Sanchez -Debkanchan Samadder -Audrius Karosevicius -Lukasz Piliszczuk -SoundReply Solutions GmbH -Rafal Wachol -Pau Picas -Christian Weder -Alexandru Tuca -Christian Weder -Rhodes Davis Jr. -Luigi Agosti -Quentin Le Guennec -Koushik Ravikumar -Nissim Dsilva -Giancarlo Rocha -Ryo Miyake -Théo Champion -Kazuki Yamaguchi -Eitan Schwartz -Chris Rutkowski -Juan Alvarez -Aleksandr Yurkovskiy -Anton Borries -Alex Li -Rahul Raj <64.rahulraj@gmail.com> diff --git a/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md b/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md deleted file mode 100644 index fc8a78af95b9..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md +++ /dev/null @@ -1,81 +0,0 @@ -## NEXT - -* Updates code for `no_leading_underscores_for_local_identifiers` lint. -* Updates minimum Flutter version to 2.10. - -## 2.0.4 - -* Removes unnecessary imports. -* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors - lint warnings. - -## 2.0.3 - -* Switches to an in-package method channel implementation. -* Fixes newly enabled analyzer options. - -## 2.0.2 - -* Add native unit tests. -* Updated installation instructions in README. - -## 2.0.1 - -* Add `implements` to the pubspec. - -## 2.0.0 - -* Migrate to null safety. - -## 0.0.1+12 - -* Update Flutter SDK constraint. - -## 0.0.1+11 - -* Remove unused `test` dependency. -* Update Dart SDK constraint in example. - -## 0.0.1+10 - -* Remove iOS and Android folders from the example app. - -## 0.0.1+9 - -* Remove Android folder from `shared_preferences_macos`. - -## 0.0.1+8 - -* Declare API stability and compatibility with `1.0.0` (more details at: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0). - -## 0.0.1+7 - -* Fix CocoaPods podspec lint warnings. - -## 0.0.1+6 - -* Make the pedantic dev_dependency explicit. - -## 0.0.1+5 - -* Fix readme - -## 0.0.1+4 - -* Bump gradle version to avoid bugs with android projects - -## 0.0.1+3 - -* Update README. - -## 0.0.1+2 - -* Remove unused onMethodCall method. - -## 0.0.1+1 - -* Add an android/ folder with no-op implementation to workaround https://github.com/flutter/flutter/issues/46898. - -## 0.0.1 - -* Initial open source release. diff --git a/packages/shared_preferences/shared_preferences_macos/LICENSE b/packages/shared_preferences/shared_preferences_macos/LICENSE deleted file mode 100644 index c6823b81eb84..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/shared_preferences/shared_preferences_macos/README.md b/packages/shared_preferences/shared_preferences_macos/README.md deleted file mode 100644 index e9cd7f25be03..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# shared\_preferences\_macos - -The macos implementation of [`shared_preferences`][1]. - -## Usage - -This package is [endorsed][2], which means you can simply use `shared_preferences` -normally. This package will be automatically included in your app when you do. - -[1]: https://pub.dev/packages/shared_preferences -[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin diff --git a/packages/shared_preferences/shared_preferences_macos/example/README.md b/packages/shared_preferences/shared_preferences_macos/example/README.md deleted file mode 100644 index 96b8bb17dbff..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/example/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Platform Implementation Test App - -This is a test app for manual testing and automated integration testing -of this platform implementation. It is not intended to demonstrate actual use of -this package, since the intent is that plugin clients use the app-facing -package. - -Unless you are making changes to this implementation package, this example is -very unlikely to be relevant. diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/Info.plist b/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/Info.plist deleted file mode 100644 index 64d65ca49577..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift deleted file mode 100644 index 7da66cbc80df..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/example/macos/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import FlutterMacOS -import XCTest -import shared_preferences_macos - -class RunnerTests: XCTestCase { - func testHandlesCommitNoOp() throws { - let plugin = SharedPreferencesPlugin() - let call = FlutterMethodCall(methodName: "commit", arguments: nil) - var called = false - plugin.handle( - call, - result: { (result: Any?) -> Void in - called = true - XCTAssert(result as? Bool == true) - }) - XCTAssert(called) - } - - func testSetAndGet() throws { - let plugin = SharedPreferencesPlugin() - let setCall = FlutterMethodCall( - methodName: "setInt", - arguments: [ - "key": "flutter.foo", - "value": 42, - ]) - plugin.handle( - setCall, - result: { (result: Any?) -> Void in - XCTAssert(result as? Bool == true) - }) - - var value: Int? - plugin.handle( - FlutterMethodCall(methodName: "getAll", arguments: nil), - result: { (result: Any?) -> Void in - if let prefs = result as? [String: Any] { - value = prefs["flutter.foo"] as? Int - } - }) - XCTAssertEqual(value, 42) - } - - func testClear() throws { - let plugin = SharedPreferencesPlugin() - let setCall = FlutterMethodCall( - methodName: "setInt", - arguments: [ - "key": "flutter.foo", - "value": 42, - ]) - plugin.handle(setCall, result: { (result: Any?) -> Void in }) - - // Make sure there is something to clear, so the test can't pass due to a set failure. - let getCall = FlutterMethodCall(methodName: "getAll", arguments: nil) - var value: Int? - plugin.handle( - getCall, - result: { (result: Any?) -> Void in - if let prefs = result as? [String: Any] { - value = prefs["flutter.foo"] as? Int - } - }) - XCTAssertEqual(value, 42) - - // Clear the value. - plugin.handle( - FlutterMethodCall(methodName: "clear", arguments: nil), - result: { (result: Any?) -> Void in - XCTAssert(result as? Bool == true) - }) - - // Get the value again, which should clear |value|. - plugin.handle( - getCall, - result: { (result: Any?) -> Void in - if let prefs = result as? [String: Any] { - value = prefs["flutter.foo"] as? Int - XCTAssert(prefs.isEmpty) - } - }) - XCTAssertEqual(value, nil) - } -} diff --git a/packages/shared_preferences/shared_preferences_macos/example/test_driver/integration_test.dart b/packages/shared_preferences/shared_preferences_macos/example/test_driver/integration_test.dart deleted file mode 100644 index 4f10f2a522f3..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/example/test_driver/integration_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:integration_test/integration_test_driver.dart'; - -Future main() => integrationDriver(); diff --git a/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart b/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart deleted file mode 100644 index a97fe131af5c..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/lib/shared_preferences_macos.dart +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; - -const MethodChannel _kChannel = - MethodChannel('plugins.flutter.io/shared_preferences_macos'); - -/// The macOS implementation of [SharedPreferencesStorePlatform]. -/// -/// This class implements the `package:shared_preferences` functionality for macOS. -class SharedPreferencesMacOS extends SharedPreferencesStorePlatform { - /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. - static void registerWith() { - SharedPreferencesStorePlatform.instance = SharedPreferencesMacOS(); - } - - @override - Future remove(String key) async { - return (await _kChannel.invokeMethod( - 'remove', - {'key': key}, - ))!; - } - - @override - Future setValue(String valueType, String key, Object value) async { - return (await _kChannel.invokeMethod( - 'set$valueType', - {'key': key, 'value': value}, - ))!; - } - - @override - Future clear() async { - return (await _kChannel.invokeMethod('clear'))!; - } - - @override - Future> getAll() async { - final Map? preferences = - await _kChannel.invokeMapMethod('getAll'); - - if (preferences == null) { - return {}; - } - return preferences; - } -} diff --git a/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift b/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift deleted file mode 100644 index 91b42441adda..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/macos/Classes/SharedPreferencesPlugin.swift +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import FlutterMacOS -import Foundation - -public class SharedPreferencesPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "plugins.flutter.io/shared_preferences_macos", - binaryMessenger: registrar.messenger) - let instance = SharedPreferencesPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getAll": - result(getAllPrefs()) - case "setBool", - "setInt", - "setDouble", - "setString", - "setStringList": - let arguments = call.arguments as! [String: Any] - let key = arguments["key"] as! String - UserDefaults.standard.set(arguments["value"], forKey: key) - result(true) - case "commit": - // UserDefaults does not need to be synchronized. - result(true) - case "remove": - let arguments = call.arguments as! [String: Any] - let key = arguments["key"] as! String - UserDefaults.standard.removeObject(forKey: key) - result(true) - case "clear": - let defaults = UserDefaults.standard - for (key, _) in getAllPrefs() { - defaults.removeObject(forKey: key) - } - result(true) - default: - result(FlutterMethodNotImplemented) - } - } -} - -/// Returns all preferences stored by this plugin. -private func getAllPrefs() -> [String: Any] { - var filteredPrefs: [String: Any] = [:] - if let appDomain = Bundle.main.bundleIdentifier, - let prefs = UserDefaults.standard.persistentDomain(forName: appDomain) - { - for (key, value) in prefs where key.hasPrefix("flutter.") { - filteredPrefs[key] = value - } - } - return filteredPrefs -} diff --git a/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart b/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart deleted file mode 100644 index 8e71e403ca03..000000000000 --- a/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences_macos/shared_preferences_macos.dart'; -import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group(MethodChannelSharedPreferencesStore, () { - const MethodChannel channel = MethodChannel( - 'plugins.flutter.io/shared_preferences_macos', - ); - - const Map kTestValues = { - 'flutter.String': 'hello world', - 'flutter.Bool': true, - 'flutter.Int': 42, - 'flutter.Double': 3.14159, - 'flutter.StringList': ['foo', 'bar'], - }; - // Create a dummy in-memory implementation to back the mocked method channel - // API to simplify validation of the expected calls. - late InMemorySharedPreferencesStore testData; - - final List log = []; - late SharedPreferencesStorePlatform store; - - setUp(() async { - testData = InMemorySharedPreferencesStore.empty(); - - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - if (methodCall.method == 'getAll') { - return testData.getAll(); - } - if (methodCall.method == 'remove') { - final String key = (methodCall.arguments['key'] as String?)!; - return testData.remove(key); - } - if (methodCall.method == 'clear') { - return testData.clear(); - } - final RegExp setterRegExp = RegExp(r'set(.*)'); - final Match? match = setterRegExp.matchAsPrefix(methodCall.method); - if (match?.groupCount == 1) { - final String valueType = match!.group(1)!; - final String key = (methodCall.arguments['key'] as String?)!; - final Object value = (methodCall.arguments['value'] as Object?)!; - return testData.setValue(valueType, key, value); - } - fail('Unexpected method call: ${methodCall.method}'); - }); - log.clear(); - }); - - test('registers instance', () { - SharedPreferencesMacOS.registerWith(); - expect(SharedPreferencesStorePlatform.instance, - isA()); - }); - - test('getAll', () async { - store = SharedPreferencesMacOS(); - testData = InMemorySharedPreferencesStore.withData(kTestValues); - expect(await store.getAll(), kTestValues); - expect(log.single.method, 'getAll'); - }); - - test('remove', () async { - store = SharedPreferencesMacOS(); - testData = InMemorySharedPreferencesStore.withData(kTestValues); - expect(await store.remove('flutter.String'), true); - expect(await store.remove('flutter.Bool'), true); - expect(await store.remove('flutter.Int'), true); - expect(await store.remove('flutter.Double'), true); - expect(await testData.getAll(), { - 'flutter.StringList': ['foo', 'bar'], - }); - - expect(log, hasLength(4)); - for (final MethodCall call in log) { - expect(call.method, 'remove'); - } - }); - - test('setValue', () async { - store = SharedPreferencesMacOS(); - expect(await testData.getAll(), isEmpty); - for (final String key in kTestValues.keys) { - final Object value = kTestValues[key]!; - expect(await store.setValue(key.split('.').last, key, value), true); - } - expect(await testData.getAll(), kTestValues); - - expect(log, hasLength(5)); - expect(log[0].method, 'setString'); - expect(log[1].method, 'setBool'); - expect(log[2].method, 'setInt'); - expect(log[3].method, 'setDouble'); - expect(log[4].method, 'setStringList'); - }); - - test('clear', () async { - store = SharedPreferencesMacOS(); - testData = InMemorySharedPreferencesStore.withData(kTestValues); - expect(await testData.getAll(), isNotEmpty); - expect(await store.clear(), true); - expect(await testData.getAll(), isEmpty); - expect(log.single.method, 'clear'); - }); - }); -} diff --git a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md index 3ef89c396222..38cdf083ccda 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.1.0 diff --git a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml index b55eb1ccceb2..59d6409cff7a 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart index ed4ecc7f27e3..da333cf7f234 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart @@ -31,13 +31,20 @@ void main() { setUp(() async { testData = InMemorySharedPreferencesStore.empty(); + Map getArgumentDictionary(MethodCall call) { + return (call.arguments as Map) + .cast(); + } + channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); if (methodCall.method == 'getAll') { return testData.getAll(); } if (methodCall.method == 'remove') { - final String key = (methodCall.arguments['key'] as String?)!; + final Map arguments = + getArgumentDictionary(methodCall); + final String key = arguments['key']! as String; return testData.remove(key); } if (methodCall.method == 'clear') { @@ -47,8 +54,10 @@ void main() { final Match? match = setterRegExp.matchAsPrefix(methodCall.method); if (match?.groupCount == 1) { final String valueType = match!.group(1)!; - final String key = (methodCall.arguments['key'] as String?)!; - final Object value = (methodCall.arguments['value'] as Object?)!; + final Map arguments = + getArgumentDictionary(methodCall); + final String key = arguments['key']! as String; + final Object value = arguments['value']!; return testData.setValue(valueType, key, value); } fail('Unexpected method call: ${methodCall.method}'); diff --git a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md index 8c8411da6fff..6332663b4b47 100644 --- a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.0.4 diff --git a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml index 050275489efa..52cfa1b436fb 100644 --- a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_web/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/pubspec.yaml index b64f37d10da6..942fe12a39a1 100644 --- a/packages/shared_preferences/shared_preferences_web/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md index c486a4ce8f9b..b99e3dd6f6ec 100644 --- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.1.3 + +* Updates code for stricter lint checks. + ## 2.1.2 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart index 74d5e4c68772..e442c4b69ee5 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart @@ -66,9 +66,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml index 43c2145b32ae..bb51f7fbef18 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml index 7f97759e6364..03fc31c6301e 100644 --- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -2,11 +2,11 @@ name: shared_preferences_windows description: Windows implementation of shared_preferences repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.2 +version: 2.1.3 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 4b365b8d858d..dbb33aa34bba 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 6.1.8 + +* Updates code for stricter lint checks. + ## 6.1.7 * Updates code for new analysis options. diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index e9e4dae476cc..8fbdfb3930e1 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -38,7 +38,7 @@ void main() => runApp( Future _launchUrl() async { if (!await launchUrl(_url)) { - throw 'Could not launch $_url'; + throw Exception('Could not launch $_url'); } } ``` @@ -198,10 +198,10 @@ final String filePath = testFile.absolute.path; final Uri uri = Uri.file(filePath); if (!File(uri.toFilePath()).existsSync()) { - throw '$uri does not exist!'; + throw Exception('$uri does not exist!'); } if (!await launchUrl(uri)) { - throw 'Could not launch $uri'; + throw Exception('Could not launch $uri'); } ``` diff --git a/packages/url_launcher/url_launcher/example/lib/basic.dart b/packages/url_launcher/url_launcher/example/lib/basic.dart index 987ca2134318..422e2aae460c 100644 --- a/packages/url_launcher/url_launcher/example/lib/basic.dart +++ b/packages/url_launcher/url_launcher/example/lib/basic.dart @@ -28,7 +28,7 @@ void main() => runApp( Future _launchUrl() async { if (!await launchUrl(_url)) { - throw 'Could not launch $_url'; + throw Exception('Could not launch $_url'); } } // #enddocregion basic-example diff --git a/packages/url_launcher/url_launcher/example/lib/encoding.dart b/packages/url_launcher/url_launcher/example/lib/encoding.dart index 24c724466a77..575eb5f42387 100644 --- a/packages/url_launcher/url_launcher/example/lib/encoding.dart +++ b/packages/url_launcher/url_launcher/example/lib/encoding.dart @@ -22,8 +22,14 @@ String? encodeQueryParameters(Map params) { // #enddocregion encode-query-parameters void main() => runApp( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors MaterialApp( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors home: Material( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: const [ diff --git a/packages/url_launcher/url_launcher/example/lib/files.dart b/packages/url_launcher/url_launcher/example/lib/files.dart index d48440670406..7f9d20669ee7 100644 --- a/packages/url_launcher/url_launcher/example/lib/files.dart +++ b/packages/url_launcher/url_launcher/example/lib/files.dart @@ -38,10 +38,10 @@ Future _openFile() async { final Uri uri = Uri.file(filePath); if (!File(uri.toFilePath()).existsSync()) { - throw '$uri does not exist!'; + throw Exception('$uri does not exist!'); } if (!await launchUrl(uri)) { - throw 'Could not launch $uri'; + throw Exception('Could not launch $uri'); } // #enddocregion file } diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index a870e18a53de..9b005cf98db0 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -58,7 +58,7 @@ class _MyHomePageState extends State { url, mode: LaunchMode.externalApplication, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -69,7 +69,7 @@ class _MyHomePageState extends State { webViewConfiguration: const WebViewConfiguration( headers: {'my_header_key': 'my_header_value'}), )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -79,7 +79,7 @@ class _MyHomePageState extends State { mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableJavaScript: false), )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -89,7 +89,7 @@ class _MyHomePageState extends State { mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableDomStorage: false), )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index 573dc0d9ed01..83900bfdef75 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart index 5bd5ef63d5b4..9f6d2dca001e 100644 --- a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart +++ b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart @@ -150,5 +150,4 @@ Future closeWebView() async { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 642b4b51e90c..e2b3ed534dbf 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.7 +version: 6.1.8 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart index 40336a090ab7..b2fde31d526d 100644 --- a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart +++ b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart @@ -321,8 +321,6 @@ void main() { /// This removes the type information from a value so that it can be cast /// to another type even if that cast is redundant. -/// /// We use this so that APIs whose type have become more descriptive can still /// be used on the stable branch where they require a cast. -// TODO(ianh): Remove this once we roll stable in late 2021. Object? _anonymize(T? value) => value; diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index f2f65949e211..1062de50c4ca 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 6.0.23 + +* Updates code for stricter lint checks. + ## 6.0.22 * Updates code for new analysis options. diff --git a/packages/url_launcher/url_launcher_android/example/lib/main.dart b/packages/url_launcher/url_launcher_android/example/lib/main.dart index 68cf334d6656..7a77c86aef72 100644 --- a/packages/url_launcher/url_launcher_android/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_android/example/lib/main.dart @@ -63,7 +63,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -77,7 +77,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {'my_header_key': 'my_header_value'}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -91,7 +91,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -105,7 +105,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_android/example/pubspec.yaml b/packages/url_launcher/url_launcher_android/example/pubspec.yaml index 6c922c7a0f7d..33fc9f06ed63 100644 --- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index d096b0ccc2cc..599274a95ebc 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_android description: Android implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.0.22 +version: 6.0.23 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart index eebd8cd4c059..b8ccc2cbbee7 100644 --- a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart +++ b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart @@ -61,7 +61,8 @@ void main() { const String genericUrl = 'http://flutter.dev'; channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); - return methodCall.arguments['url'] != specificUrl; + return (methodCall.arguments as Map)['url'] != + specificUrl; }); final UrlLauncherAndroid launcher = UrlLauncherAndroid(); @@ -69,7 +70,7 @@ void main() { expect(canLaunch, true); expect(log.length, 2); - expect(log[1].arguments['url'], genericUrl); + expect((log[1].arguments as Map)['url'], genericUrl); }); test('checks a generic URL if an https URL returns false', () async { @@ -77,7 +78,8 @@ void main() { const String genericUrl = 'https://flutter.dev'; channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); - return methodCall.arguments['url'] != specificUrl; + return (methodCall.arguments as Map)['url'] != + specificUrl; }); final UrlLauncherAndroid launcher = UrlLauncherAndroid(); @@ -85,7 +87,7 @@ void main() { expect(canLaunch, true); expect(log.length, 2); - expect(log[1].arguments['url'], genericUrl); + expect((log[1].arguments as Map)['url'], genericUrl); }); test('does not a generic URL if a non-web URL returns false', () async { diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index cf018da4f59d..058520764c88 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 6.0.18 + +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 6.0.17 diff --git a/packages/url_launcher/url_launcher_ios/example/lib/main.dart b/packages/url_launcher/url_launcher_ios/example/lib/main.dart index 6e8ce7431a66..08a03063a880 100644 --- a/packages/url_launcher/url_launcher_ios/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_ios/example/lib/main.dart @@ -53,7 +53,7 @@ class _MyHomePageState extends State { headers: {'my_header_key': 'my_header_value'}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -70,7 +70,7 @@ class _MyHomePageState extends State { headers: {'my_header_key': 'my_header_value'}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -87,7 +87,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -104,7 +104,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -155,7 +155,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml index 9a134c747fa4..d170d3617442 100644 --- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 5e06a80b4cfe..5817a9a44051 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/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.0.17 +version: 6.0.18 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index 69d8b24ddf6f..3d955871c8c8 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 3.0.2 + +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 3.0.1 diff --git a/packages/url_launcher/url_launcher_linux/example/lib/main.dart b/packages/url_launcher/url_launcher_linux/example/lib/main.dart index 0b985e78ac0d..bbe651ea05de 100644 --- a/packages/url_launcher/url_launcher_linux/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_linux/example/lib/main.dart @@ -50,7 +50,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml index 17effeb1ffcb..ba738806af38 100644 --- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 99b237506c60..e455ab83bef5 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.1 +version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index 7386ecced865..eb42ba920e23 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 3.0.2 + +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 3.0.1 diff --git a/packages/url_launcher/url_launcher_macos/example/lib/main.dart b/packages/url_launcher/url_launcher_macos/example/lib/main.dart index 0b985e78ac0d..bbe651ea05de 100644 --- a/packages/url_launcher/url_launcher_macos/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_macos/example/lib/main.dart @@ -50,7 +50,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml index 3b802ea229ba..688cac3a6b0e 100644 --- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index eaf210a367b6..2ec915fc2ddb 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_macos description: macOS implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.1 +version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index d45ca36e3906..fecd2a45c4cb 100644 --- a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.1 * Updates imports for `prefer_relative_imports`. diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/link.dart b/packages/url_launcher/url_launcher_platform_interface/lib/link.dart index da8aa1570bad..bddadad893a7 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/link.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/link.dart @@ -109,5 +109,4 @@ Future pushRouteNameToFramework(Object? _, String routeName) { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index 4364e116c508..ab37dc32eedd 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.1.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 5454338bde51..51b2de90b88a 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +* Updates minimum Flutter version to 3.0. + +## 2.0.14 + +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 2.0.13 diff --git a/packages/url_launcher/url_launcher_web/example/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml index f972b2857ecf..ca1b0d6634a7 100644 --- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_web/lib/src/link.dart b/packages/url_launcher/url_launcher_web/lib/src/link.dart index 112d07ea7571..78c049c03def 100644 --- a/packages/url_launcher/url_launcher_web/lib/src/link.dart +++ b/packages/url_launcher/url_launcher_web/lib/src/link.dart @@ -247,9 +247,13 @@ class LinkViewController extends PlatformViewController { return '_self'; case LinkTarget.blank: return '_blank'; - default: - throw Exception('Unknown LinkTarget value $target.'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + return '_self'; } @override diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 6d4c80689427..8c8214ef6e4b 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_web description: Web platform implementation of url_launcher repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 2.0.13 +version: 2.0.14 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index a5952feb4978..abb3ab10db57 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,5 +1,11 @@ -## NEXT +## 3.0.3 +* Converts internal implentation to Pigeon. +* Updates minimum Flutter version to 3.0. + +## 3.0.2 + +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 3.0.1 diff --git a/packages/url_launcher/url_launcher_windows/example/lib/main.dart b/packages/url_launcher/url_launcher_windows/example/lib/main.dart index 0b985e78ac0d..bbe651ea05de 100644 --- a/packages/url_launcher/url_launcher_windows/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_windows/example/lib/main.dart @@ -50,7 +50,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml index 966d32c779e8..231d3d0848bc 100644 --- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart new file mode 100644 index 000000000000..a1d46c11267d --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class UrlLauncherApi { + /// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + UrlLauncherApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future canLaunchUrl(String arg_url) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future launchUrl(String arg_url) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart index b0ee8cb1a0b4..41c403e56f8e 100644 --- a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart +++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart @@ -2,17 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/url_launcher_windows'); +import 'src/messages.g.dart'; /// An implementation of [UrlLauncherPlatform] for Windows. class UrlLauncherWindows extends UrlLauncherPlatform { + /// Creates a new plugin implementation instance. + UrlLauncherWindows({ + @visibleForTesting UrlLauncherApi? api, + }) : _hostApi = api ?? UrlLauncherApi(); + + final UrlLauncherApi _hostApi; + /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherWindows(); @@ -23,10 +27,7 @@ class UrlLauncherWindows extends UrlLauncherPlatform { @override Future canLaunch(String url) { - return _channel.invokeMethod( - 'canLaunch', - {'url': url}, - ).then((bool? value) => value ?? false); + return _hostApi.canLaunchUrl(url); } @override @@ -39,16 +40,9 @@ class UrlLauncherWindows extends UrlLauncherPlatform { required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, - }) { - return _channel.invokeMethod( - 'launch', - { - 'url': url, - 'enableJavaScript': enableJavaScript, - 'enableDomStorage': enableDomStorage, - 'universalLinksOnly': universalLinksOnly, - 'headers': headers, - }, - ).then((bool? value) => value ?? false); + }) async { + await _hostApi.launchUrl(url); + // Failure is handled via a PlatformException from `launchUrl`. + return true; } } diff --git a/packages/url_launcher/url_launcher_windows/pigeons/copyright.txt b/packages/url_launcher/url_launcher_windows/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/url_launcher/url_launcher_windows/pigeons/messages.dart b/packages/url_launcher/url_launcher_windows/pigeons/messages.dart new file mode 100644 index 000000000000..9607cdffc686 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/pigeons/messages.dart @@ -0,0 +1,18 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + cppOptions: CppOptions(namespace: 'url_launcher_windows'), + cppHeaderOut: 'windows/messages.g.h', + cppSourceOut: 'windows/messages.g.cpp', + copyrightHeader: 'pigeons/copyright.txt', +)) +@HostApi(dartHostTestHandler: 'TestUrlLauncherApi') +abstract class UrlLauncherApi { + bool canLaunchUrl(String url); + void launchUrl(String url); +} diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index b35b62e1d82e..de4f5edd69eb 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_windows description: Windows implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.1 +version: 3.0.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: @@ -24,4 +24,5 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^5.0.1 test: ^1.16.3 diff --git a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart index 8b55b29bb530..7f48f64fa92c 100644 --- a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart +++ b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart @@ -5,140 +5,101 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +import 'package:url_launcher_windows/src/messages.g.dart'; import 'package:url_launcher_windows/url_launcher_windows.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('$UrlLauncherWindows', () { - const MethodChannel channel = - MethodChannel('plugins.flutter.io/url_launcher_windows'); - final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - - // Return null explicitly instead of relying on the implicit null - // returned by the method channel if no return statement is specified. - return null; - }); + late _FakeUrlLauncherApi api; + late UrlLauncherWindows plugin; - test('registers instance', () { - UrlLauncherWindows.registerWith(); - expect(UrlLauncherPlatform.instance, isA()); - }); + setUp(() { + api = _FakeUrlLauncherApi(); + plugin = UrlLauncherWindows(api: api); + }); - tearDown(() { - log.clear(); - }); + test('registers instance', () { + UrlLauncherWindows.registerWith(); + expect(UrlLauncherPlatform.instance, isA()); + }); - test('canLaunch', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.canLaunch('http://example.com/'); - expect( - log, - [ - isMethodCall('canLaunch', arguments: { - 'url': 'http://example.com/', - }) - ], - ); - }); + group('canLaunch', () { + test('handles true', () async { + api.canLaunch = true; - test('canLaunch should return false if platform returns null', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - final bool canLaunch = await launcher.canLaunch('http://example.com/'); + final bool result = await plugin.canLaunch('http://example.com/'); - expect(canLaunch, false); + expect(result, isTrue); + expect(api.argument, 'http://example.com/'); }); - test('launch', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {}, - ); - expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], - ); - }); + test('handles false', () async { + api.canLaunch = false; - test('launch with headers', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {'key': 'value'}, - ); - expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {'key': 'value'}, - }) - ], - ); + final bool result = await plugin.canLaunch('http://example.com/'); + + expect(result, isFalse); + expect(api.argument, 'http://example.com/'); }); + }); + + group('launch', () { + test('handles success', () async { + api.canLaunch = true; - test('launch universal links only', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.launch( - 'http://example.com/', - useSafariVC: false, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: true, - headers: const {}, - ); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': true, - 'headers': {}, - }) - ], - ); + plugin.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + completes); + expect(api.argument, 'http://example.com/'); }); - test('launch should return false if platform returns null', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - final bool launched = await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {}, - ); - - expect(launched, false); + test('handles failure', () async { + api.canLaunch = false; + + await expectLater( + plugin.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA())); + expect(api.argument, 'http://example.com/'); }); }); } + +class _FakeUrlLauncherApi implements UrlLauncherApi { + /// The argument that was passed to an API call. + String? argument; + + /// Controls the behavior of the fake implementations. + /// + /// - [canLaunchUrl] returns this value. + /// - [launchUrl] throws if this is false. + bool canLaunch = false; + + @override + Future canLaunchUrl(String url) async { + argument = url; + return canLaunch; + } + + @override + Future launchUrl(String url) async { + argument = url; + if (!canLaunch) { + throw PlatformException(code: 'Failed'); + } + } +} diff --git a/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt index a4185acff6a1..a34bcb3d35da 100644 --- a/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt +++ b/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt @@ -5,6 +5,8 @@ project(${PROJECT_NAME} LANGUAGES CXX) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES + "messages.g.cpp" + "messages.g.h" "system_apis.cpp" "system_apis.h" "url_launcher_plugin.cpp" diff --git a/packages/url_launcher/url_launcher_windows/windows/messages.g.cpp b/packages/url_launcher/url_launcher_windows/windows/messages.g.cpp new file mode 100644 index 000000000000..eb1cf792931f --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/messages.g.cpp @@ -0,0 +1,113 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace url_launcher_windows { + +/// The codec used by UrlLauncherApi. +const flutter::StandardMessageCodec& UrlLauncherApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &flutter::StandardCodecSerializer::GetInstance()); +} + +// Sets up an instance of `UrlLauncherApi` to handle messages through the +// `binary_messenger`. +void UrlLauncherApi::SetUp(flutter::BinaryMessenger* binary_messenger, + UrlLauncherApi* api) { + { + auto channel = + std::make_unique>( + binary_messenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_url_arg = args.at(0); + if (encodable_url_arg.IsNull()) { + reply(WrapError("url_arg unexpectedly null.")); + return; + } + const auto& url_arg = std::get(encodable_url_arg); + ErrorOr output = api->CanLaunchUrl(url_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back( + flutter::EncodableValue(std::move(output).TakeValue())); + reply(flutter::EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } + { + auto channel = + std::make_unique>( + binary_messenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_url_arg = args.at(0); + if (encodable_url_arg.IsNull()) { + reply(WrapError("url_arg unexpectedly null.")); + return; + } + const auto& url_arg = std::get(encodable_url_arg); + std::optional output = api->LaunchUrl(url_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back(flutter::EncodableValue()); + reply(flutter::EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } +} + +flutter::EncodableValue UrlLauncherApi::WrapError( + std::string_view error_message) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(std::string(error_message)), + flutter::EncodableValue("Error"), flutter::EncodableValue()}); +} +flutter::EncodableValue UrlLauncherApi::WrapError(const FlutterError& error) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(error.message()), + flutter::EncodableValue(error.code()), error.details()}); +} + +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/messages.g.h b/packages/url_launcher/url_launcher_windows/windows/messages.g.h new file mode 100644 index 000000000000..cb8e95f8d065 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/messages.g.h @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_H_ +#define PIGEON_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace url_launcher_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + explicit FlutterError(const std::string& code, const std::string& message) + : code_(code), message_(message) {} + explicit FlutterError(const std::string& code, const std::string& message, + const flutter::EncodableValue& details) + : code_(code), message_(message), details_(details) {} + + const std::string& code() const { return code_; } + const std::string& message() const { return message_; } + const flutter::EncodableValue& details() const { return details_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) { new (&v_) T(rhs); } + ErrorOr(const T&& rhs) { v_ = std::move(rhs); } + ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); } + ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); } + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class UrlLauncherApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class UrlLauncherApi { + public: + UrlLauncherApi(const UrlLauncherApi&) = delete; + UrlLauncherApi& operator=(const UrlLauncherApi&) = delete; + virtual ~UrlLauncherApi(){}; + virtual ErrorOr CanLaunchUrl(const std::string& url) = 0; + virtual std::optional LaunchUrl(const std::string& url) = 0; + + // The codec used by UrlLauncherApi. + static const flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `UrlLauncherApi` to handle messages through the + // `binary_messenger`. + static void SetUp(flutter::BinaryMessenger* binary_messenger, + UrlLauncherApi* api); + static flutter::EncodableValue WrapError(std::string_view error_message); + static flutter::EncodableValue WrapError(const FlutterError& error); + + protected: + UrlLauncherApi() = default; +}; + +} // namespace url_launcher_windows + +#endif // PIGEON_H_ diff --git a/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp b/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp index abd690b6e47f..cde95ee1b399 100644 --- a/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp @@ -5,7 +5,7 @@ #include -namespace url_launcher_plugin { +namespace url_launcher_windows { SystemApis::SystemApis() {} @@ -35,4 +35,4 @@ HINSTANCE SystemApisImpl::ShellExecuteW(HWND hwnd, LPCWSTR operation, show_flags); } -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/system_apis.h b/packages/url_launcher/url_launcher_windows/windows/system_apis.h index 7b56704d8e04..c56c4100180b 100644 --- a/packages/url_launcher/url_launcher_windows/windows/system_apis.h +++ b/packages/url_launcher/url_launcher_windows/windows/system_apis.h @@ -3,7 +3,7 @@ // found in the LICENSE file. #include -namespace url_launcher_plugin { +namespace url_launcher_windows { // An interface wrapping system APIs used by the plugin, for mocking. class SystemApis { @@ -53,4 +53,4 @@ class SystemApisImpl : public SystemApis { int show_flags); }; -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp b/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp index 191d51a0caa8..9dd2be5347b5 100644 --- a/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp @@ -9,11 +9,13 @@ #include #include +#include #include +#include "messages.g.h" #include "url_launcher_plugin.h" -namespace url_launcher_plugin { +namespace url_launcher_windows { namespace test { namespace { @@ -42,30 +44,10 @@ class MockSystemApis : public SystemApis { (override)); }; -class MockMethodResult : public flutter::MethodResult<> { - public: - MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result), - (override)); - MOCK_METHOD(void, ErrorInternal, - (const std::string& error_code, const std::string& error_message, - const EncodableValue* details), - (override)); - MOCK_METHOD(void, NotImplementedInternal, (), (override)); -}; - -std::unique_ptr CreateArgumentsWithUrl(const std::string& url) { - EncodableMap args = { - {EncodableValue("url"), EncodableValue(url)}, - }; - return std::make_unique(args); -} - } // namespace TEST(UrlLauncherPlugin, CanLaunchSuccessTrue) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return success values from the registery commands. HKEY fake_key = reinterpret_cast(1); @@ -73,20 +55,16 @@ TEST(UrlLauncherPlugin, CanLaunchSuccessTrue) { .WillOnce(DoAll(SetArgPointee<4>(fake_key), Return(ERROR_SUCCESS))); EXPECT_CALL(*system, RegQueryValueExW).WillOnce(Return(ERROR_SUCCESS)); EXPECT_CALL(*system, RegCloseKey(fake_key)).WillOnce(Return(ERROR_SUCCESS)); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("canLaunch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + ErrorOr result = plugin.CanLaunchUrl("https://some.url.com"); + + ASSERT_FALSE(result.has_error()); + EXPECT_TRUE(result.value()); } TEST(UrlLauncherPlugin, CanLaunchQueryFailure) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return success values from the registery commands, except for the query, // to simulate a scheme that is in the registry, but has no URL handler. @@ -95,68 +73,52 @@ TEST(UrlLauncherPlugin, CanLaunchQueryFailure) { .WillOnce(DoAll(SetArgPointee<4>(fake_key), Return(ERROR_SUCCESS))); EXPECT_CALL(*system, RegQueryValueExW).WillOnce(Return(ERROR_FILE_NOT_FOUND)); EXPECT_CALL(*system, RegCloseKey(fake_key)).WillOnce(Return(ERROR_SUCCESS)); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("canLaunch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + ErrorOr result = plugin.CanLaunchUrl("https://some.url.com"); + + ASSERT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } TEST(UrlLauncherPlugin, CanLaunchHandlesOpenFailure) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return failure for opening. EXPECT_CALL(*system, RegOpenKeyExW).WillOnce(Return(ERROR_BAD_PATHNAME)); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("canLaunch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + ErrorOr result = plugin.CanLaunchUrl("https://some.url.com"); + + ASSERT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } TEST(UrlLauncherPlugin, LaunchSuccess) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return a success value (>32) from launching. EXPECT_CALL(*system, ShellExecuteW) .WillOnce(Return(reinterpret_cast(33))); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("launch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + std::optional error = plugin.LaunchUrl("https://some.url.com"); + + EXPECT_FALSE(error.has_value()); } TEST(UrlLauncherPlugin, LaunchReportsFailure) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return a faile value (<=32) from launching. EXPECT_CALL(*system, ShellExecuteW) .WillOnce(Return(reinterpret_cast(32))); - // Expect an error response. - EXPECT_CALL(*result, ErrorInternal); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("launch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + std::optional error = plugin.LaunchUrl("https://some.url.com"); + + EXPECT_TRUE(error.has_value()); } } // namespace test -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp index d5f201219c75..1dfee16c4445 100644 --- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp @@ -13,7 +13,9 @@ #include #include -namespace url_launcher_plugin { +#include "messages.g.h" + +namespace url_launcher_windows { namespace { @@ -62,18 +64,9 @@ std::string GetUrlArgument(const flutter::MethodCall<>& method_call) { // static void UrlLauncherPlugin::RegisterWithRegistrar( flutter::PluginRegistrar* registrar) { - auto channel = std::make_unique>( - registrar->messenger(), "plugins.flutter.io/url_launcher_windows", - &flutter::StandardMethodCodec::GetInstance()); - std::unique_ptr plugin = std::make_unique(); - - channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto& call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - + UrlLauncherApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } @@ -85,37 +78,7 @@ UrlLauncherPlugin::UrlLauncherPlugin(std::unique_ptr system_apis) UrlLauncherPlugin::~UrlLauncherPlugin() = default; -void UrlLauncherPlugin::HandleMethodCall( - const flutter::MethodCall<>& method_call, - std::unique_ptr> result) { - if (method_call.method_name().compare("launch") == 0) { - std::string url = GetUrlArgument(method_call); - if (url.empty()) { - result->Error("argument_error", "No URL provided"); - return; - } - - std::optional error = LaunchUrl(url); - if (error) { - result->Error("open_error", error.value()); - return; - } - result->Success(EncodableValue(true)); - } else if (method_call.method_name().compare("canLaunch") == 0) { - std::string url = GetUrlArgument(method_call); - if (url.empty()) { - result->Error("argument_error", "No URL provided"); - return; - } - - bool can_launch = CanLaunchUrl(url); - result->Success(EncodableValue(can_launch)); - } else { - result->NotImplemented(); - } -} - -bool UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { +ErrorOr UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { size_t separator_location = url.find(":"); if (separator_location == std::string::npos) { return false; @@ -134,7 +97,7 @@ bool UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { return has_handler; } -std::optional UrlLauncherPlugin::LaunchUrl( +std::optional UrlLauncherPlugin::LaunchUrl( const std::string& url) { std::wstring url_wide = Utf16FromUtf8(url); @@ -147,9 +110,9 @@ std::optional UrlLauncherPlugin::LaunchUrl( std::ostringstream error_message; error_message << "Failed to open " << url << ": ShellExecute error code " << status; - return std::optional(error_message.str()); + return FlutterError("open_error", error_message.str()); } return std::nullopt; } -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h index 45e70e5fc067..e51cde67ab79 100644 --- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h @@ -10,11 +10,12 @@ #include #include +#include "messages.g.h" #include "system_apis.h" -namespace url_launcher_plugin { +namespace url_launcher_windows { -class UrlLauncherPlugin : public flutter::Plugin { +class UrlLauncherPlugin : public flutter::Plugin, public UrlLauncherApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar); @@ -31,18 +32,12 @@ class UrlLauncherPlugin : public flutter::Plugin { UrlLauncherPlugin(const UrlLauncherPlugin&) = delete; UrlLauncherPlugin& operator=(const UrlLauncherPlugin&) = delete; - // Called when a method is called on the plugin channel. - void HandleMethodCall(const flutter::MethodCall<>& method_call, - std::unique_ptr> result); + // UrlLauncherApi: + ErrorOr CanLaunchUrl(const std::string& url) override; + std::optional LaunchUrl(const std::string& url) override; private: - // Returns whether or not the given URL has a registered handler. - bool CanLaunchUrl(const std::string& url); - - // Attempts to launch the given URL. On failure, returns an error string. - std::optional LaunchUrl(const std::string& url); - std::unique_ptr system_apis_; }; -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp b/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp index 05de586d8fe0..726709386fa6 100644 --- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp @@ -9,7 +9,7 @@ void UrlLauncherWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { - url_launcher_plugin::UrlLauncherPlugin::RegisterWithRegistrar( + url_launcher_windows::UrlLauncherPlugin::RegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index c91694627bb8..eed3b6bc2346 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,15 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.5.1 + +* Updates code for stricter lint checks. + +## 2.5.0 + +* Exposes `VideoScrubber` so it can be used to make custom video progress indicators + ## 2.4.10 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player/example/android/app/build.gradle b/packages/video_player/video_player/example/android/app/build.gradle index 338eeb8944f7..8b2086b6c05e 100644 --- a/packages/video_player/video_player/example/android/app/build.gradle +++ b/packages/video_player/video_player/example/android/app/build.gradle @@ -60,7 +60,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.8.1' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/packages/video_player/video_player/example/pubspec.yaml b/packages/video_player/video_player/example/pubspec.yaml index 7b6aa09329fa..0b30e9fb01e7 100644 --- a/packages/video_player/video_player/example/pubspec.yaml +++ b/packages/video_player/video_player/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 76f865aa7359..5720e2d9d136 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -697,17 +697,13 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.paused: - _wasPlayingBeforePause = _controller.value.isPlaying; - _controller.pause(); - break; - case AppLifecycleState.resumed: - if (_wasPlayingBeforePause) { - _controller.play(); - } - break; - default: + if (state == AppLifecycleState.paused) { + _wasPlayingBeforePause = _controller.value.isPlaying; + _controller.pause(); + } else if (state == AppLifecycleState.resumed) { + if (_wasPlayingBeforePause) { + _controller.play(); + } } } @@ -835,20 +831,29 @@ class VideoProgressColors { final Color backgroundColor; } -class _VideoScrubber extends StatefulWidget { - const _VideoScrubber({ +/// A scrubber to control [VideoPlayerController]s +class VideoScrubber extends StatefulWidget { + /// Create a [VideoScrubber] handler with the given [child]. + /// + /// [controller] is the [VideoPlayerController] that will be controlled by + /// this scrubber. + const VideoScrubber({ + Key? key, required this.child, required this.controller, - }); + }) : super(key: key); + /// The widget that will be displayed inside the gesture detector. final Widget child; + + /// The [VideoPlayerController] that will be controlled by this scrubber. final VideoPlayerController controller; @override - _VideoScrubberState createState() => _VideoScrubberState(); + State createState() => _VideoScrubberState(); } -class _VideoScrubberState extends State<_VideoScrubber> { +class _VideoScrubberState extends State { bool _controllerWasPlaying = false; VideoPlayerController get controller => widget.controller; @@ -1013,7 +1018,7 @@ class _VideoProgressIndicatorState extends State { child: progressIndicator, ); if (widget.allowScrubbing) { - return _VideoScrubber( + return VideoScrubber( controller: controller, child: paddedProgressIndicator, ); @@ -1095,5 +1100,4 @@ class ClosedCaption extends StatelessWidget { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index a049f0bc770e..d75456ace469 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,11 +3,11 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.4.10 +version: 2.5.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index bd71d5fa11e6..56024c4ba233 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.3.10 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player_android/android/build.gradle b/packages/video_player/video_player_android/android/build.gradle index 2677050d303b..daf3b8f4b83e 100644 --- a/packages/video_player/video_player_android/android/build.gradle +++ b/packages/video_player/video_player_android/android/build.gradle @@ -50,7 +50,7 @@ android { implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.18.1' testImplementation 'junit:junit:4.13.2' testImplementation 'androidx.test:core:1.3.0' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'org.robolectric:robolectric:4.8.1' } diff --git a/packages/video_player/video_player_android/example/android/app/build.gradle b/packages/video_player/video_player_android/example/android/app/build.gradle index 93caaa5c7c61..80de4a1ee27d 100644 --- a/packages/video_player/video_player_android/example/android/app/build.gradle +++ b/packages/video_player/video_player_android/example/android/app/build.gradle @@ -60,7 +60,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.8.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/packages/video_player/video_player_android/example/pubspec.yaml b/packages/video_player/video_player_android/example/pubspec.yaml index 29865ed3cc24..16ffe17e7ba3 100644 --- a/packages/video_player/video_player_android/example/pubspec.yaml +++ b/packages/video_player/video_player_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index bf552f9369df..90c9fbb61ea0 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', javaOut: 'android/src/main/java/io/flutter/plugins/videoplayer/Messages.java', javaOptions: JavaOptions( package: 'io.flutter.plugins.videoplayer', diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 2e1321f3bb43..3f46ec8a4d79 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.3.10 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index fad9617ddad9..fa7ca7aa7f7a 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -12,7 +12,7 @@ import 'package:video_player_android/src/messages.g.dart'; import 'package:video_player_android/video_player_android.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; @@ -360,5 +360,4 @@ void main() { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player_android/test/test_api.dart b/packages/video_player/video_player_android/test/test_api.g.dart similarity index 100% rename from packages/video_player/video_player_android/test/test_api.dart rename to packages/video_player/video_player_android/test/test_api.g.dart diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index cf9c035abcda..b8564c0a2236 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.3.8 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player_avfoundation/example/pubspec.yaml b/packages/video_player/video_player_avfoundation/example/pubspec.yaml index f101b697a9cb..422fb91e35e5 100644 --- a/packages/video_player/video_player_avfoundation/example/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index e6eda5960f29..695ff34e3ebd 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 116edde94955..a5204137af20 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.3.8 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart index ea81d438ad75..e7c3b5ba4ff3 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart @@ -12,7 +12,7 @@ import 'package:video_player_avfoundation/src/messages.g.dart'; import 'package:video_player_avfoundation/video_player_avfoundation.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; @@ -339,5 +339,4 @@ void main() { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player_avfoundation/test/test_api.dart b/packages/video_player/video_player_avfoundation/test/test_api.g.dart similarity index 100% rename from packages/video_player/video_player_avfoundation/test/test_api.dart rename to packages/video_player/video_player_avfoundation/test/test_api.g.dart diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index fb7a3b340ca9..e1acbf578027 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 6.0.1 * Fixes comment describing file URI construction. diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index 36c0a7181845..8c6a8f400bb2 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 6.0.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 1f6dc951a6ff..42355439ce12 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.13 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player_web/example/pubspec.yaml b/packages/video_player/video_player_web/example/pubspec.yaml index 1d12b4ffcfd4..c4de1ce54c1a 100644 --- a/packages/video_player/video_player_web/example/pubspec.yaml +++ b/packages/video_player/video_player_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 0cd1c11f7f38..5e603034dd28 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.13 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 329ce485d129..6d2e860e29ec 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,15 @@ +## 4.0.3 + +* Updates example code for `use_build_context_synchronously` lint. + +## 4.0.2 + +* Updates code for stricter lint checks. + +## 4.0.1 + +* Exposes `WebResourceErrorType` from platform interface. + ## 4.0.0 * **BREAKING CHANGE** Updates implementation to use the `2.0.0` release of diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index 239b417c4e04..ec1ce4eef16c 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -180,9 +180,11 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } }, child: const Icon(Icons.favorite), ); @@ -330,25 +332,29 @@ class SampleMenu extends StatelessWidget { Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Cookies:'), - _getCookieList(cookies), - ], - ), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } } Future _onAddToCache(BuildContext context) async { await webViewController.runJavaScript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', ); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Added a test entry to cache.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } } Future _onListCache() { @@ -361,9 +367,11 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } } Future _onClearCookies(BuildContext context) async { @@ -372,9 +380,11 @@ class SampleMenu extends StatelessWidget { if (!hadCookies) { message = 'There are no cookies.'; } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } } Future _onNavigationDelegateExample() { @@ -467,10 +477,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } } }, ), @@ -480,10 +491,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No forward history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } } }, ), diff --git a/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart index 8d7baa9fa5af..d210e1e7669a 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart @@ -127,6 +127,7 @@ class WebView extends StatefulWidget { case TargetPlatform.iOS: _platform = CupertinoWebView(); break; + // ignore: no_default_cases default: throw UnsupportedError( "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); diff --git a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart index 7b8301db2d4a..112966d47760 100644 --- a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart @@ -20,6 +20,7 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte ProgressCallback, WebResourceError, WebResourceErrorCallback, + WebResourceErrorType, WebViewCookie, WebViewPlatform; diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 58c0411ad7dd..a494f9e9276c 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 4.0.0 +version: 4.0.3 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart index d13b51c57955..68b60ec82896 100644 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart @@ -39,6 +39,8 @@ void main() { main_file.WebResourceErrorCallback; // ignore: unnecessary_statements main_file.WebViewCookie; + // ignore: unnecessary_statements + main_file.WebResourceErrorType; }); }); } diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index b5502ecd2577..136d71485a0f 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,33 @@ +## 3.2.4 + +* Renames Pigeon output files. + +## 3.2.3 + +* Fixes bug that prevented the web view from being garbage collected. +* Fixes bug causing a `LateInitializationError` when a `PlatformNavigationDelegate` is not provided. + +## 3.2.2 + +* Updates example code for `use_build_context_synchronously` lint. + +## 3.2.1 + +* Updates code for stricter lint checks. + +## 3.2.0 + +* Adds support for handling file selection. See `AndroidWebViewController.setOnShowFileSelector`. +* Updates pigeon dev dependency to `4.2.14`. + +## 3.1.3 + +* Fixes crash when the Java `InstanceManager` was used after plugin was removed from the engine. + +## 3.1.2 + +* Fixes bug where an `AndroidWebViewController` couldn't be reused with a new `WebViewWidget`. + ## 3.1.1 * Fixes bug where a `AndroidNavigationDelegate` was required to load a request. diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index 7384f8d453da..f053954e5755 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -39,7 +39,7 @@ android { implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.webkit:webkit:1.5.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.8.0' + testImplementation 'org.mockito:mockito-inline:5.1.0' testImplementation 'androidx.test:core:1.3.0' } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java new file mode 100644 index 000000000000..679785949697 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.os.Build; +import android.webkit.WebChromeClient; +import androidx.annotation.RequiresApi; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Arrays; + +/** + * Flutter Api implementation for {@link android.webkit.WebChromeClient.FileChooserParams}. + * + *

Passes arguments of callbacks methods from a {@link + * android.webkit.WebChromeClient.FileChooserParams} to Dart. + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class FileChooserParamsFlutterApiImpl + extends GeneratedAndroidWebView.FileChooserParamsFlutterApi { + private final InstanceManager instanceManager; + + /** + * Creates a Flutter api that sends messages to Dart. + * + * @param binaryMessenger handles sending messages to Dart + * @param instanceManager maintains instances stored to communicate with Dart objects + */ + public FileChooserParamsFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + private static GeneratedAndroidWebView.FileChooserModeEnumData toFileChooserEnumData(int mode) { + final GeneratedAndroidWebView.FileChooserModeEnumData.Builder builder = + new GeneratedAndroidWebView.FileChooserModeEnumData.Builder(); + + switch (mode) { + case WebChromeClient.FileChooserParams.MODE_OPEN: + builder.setValue(GeneratedAndroidWebView.FileChooserMode.OPEN); + break; + case WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE: + builder.setValue(GeneratedAndroidWebView.FileChooserMode.OPEN_MULTIPLE); + break; + case WebChromeClient.FileChooserParams.MODE_SAVE: + builder.setValue(GeneratedAndroidWebView.FileChooserMode.SAVE); + break; + default: + throw new IllegalArgumentException(String.format("Unsupported FileChooserMode: %d", mode)); + } + + return builder.build(); + } + + /** + * Stores the FileChooserParams instance and notifies Dart to create a new FileChooserParams + * instance that is attached to this one. + * + * @return the instanceId of the stored instance + */ + public long create(WebChromeClient.FileChooserParams instance, Reply callback) { + final long instanceId = instanceManager.addHostCreatedInstance(instance); + create( + instanceId, + instance.isCaptureEnabled(), + Arrays.asList(instance.getAcceptTypes()), + toFileChooserEnumData(instance.getMode()), + instance.getFilenameHint(), + callback); + return instanceId; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 15c80cc0a907..425f6c1415bd 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v4.2.3), do not edit directly. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.webviewflutter; @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,6 +25,90 @@ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class GeneratedAndroidWebView { + /** + * Mode of how to select files for a file chooser. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. + */ + public enum FileChooserMode { + /** + * Open single file and requires that the file exists before allowing the user to pick it. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + */ + OPEN(0), + /** + * Similar to [open] but allows multiple files to be selected. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + */ + OPEN_MULTIPLE(1), + /** + * Allows picking a nonexistent file and saving it. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + */ + SAVE(2); + + private final int index; + + private FileChooserMode(final int index) { + this.index = index; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static class FileChooserModeEnumData { + private @NonNull FileChooserMode value; + + public @NonNull FileChooserMode getValue() { + return value; + } + + public void setValue(@NonNull FileChooserMode setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"value\" is null."); + } + this.value = setterArg; + } + + /** Constructor is private to enforce null safety; use Builder. */ + private FileChooserModeEnumData() {} + + public static final class Builder { + private @Nullable FileChooserMode value; + + public @NonNull Builder setValue(@NonNull FileChooserMode setterArg) { + this.value = setterArg; + return this; + } + + public @NonNull FileChooserModeEnumData build() { + FileChooserModeEnumData pigeonReturn = new FileChooserModeEnumData(); + pigeonReturn.setValue(value); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(value == null ? null : value.index); + return toListResult; + } + + static @NonNull FileChooserModeEnumData fromList(@NonNull ArrayList list) { + FileChooserModeEnumData pigeonResult = new FileChooserModeEnumData(); + Object value = list.get(0); + pigeonResult.setValue(value == null ? null : FileChooserMode.values()[(int) value]); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static class WebResourceRequestData { private @NonNull String url; @@ -162,30 +245,30 @@ public static final class Builder { } @NonNull - Map toMap() { - Map toMapResult = new HashMap<>(); - toMapResult.put("url", url); - toMapResult.put("isForMainFrame", isForMainFrame); - toMapResult.put("isRedirect", isRedirect); - toMapResult.put("hasGesture", hasGesture); - toMapResult.put("method", method); - toMapResult.put("requestHeaders", requestHeaders); - return toMapResult; - } - - static @NonNull WebResourceRequestData fromMap(@NonNull Map map) { + ArrayList toList() { + ArrayList toListResult = new ArrayList(6); + toListResult.add(url); + toListResult.add(isForMainFrame); + toListResult.add(isRedirect); + toListResult.add(hasGesture); + toListResult.add(method); + toListResult.add(requestHeaders); + return toListResult; + } + + static @NonNull WebResourceRequestData fromList(@NonNull ArrayList list) { WebResourceRequestData pigeonResult = new WebResourceRequestData(); - Object url = map.get("url"); + Object url = list.get(0); pigeonResult.setUrl((String) url); - Object isForMainFrame = map.get("isForMainFrame"); + Object isForMainFrame = list.get(1); pigeonResult.setIsForMainFrame((Boolean) isForMainFrame); - Object isRedirect = map.get("isRedirect"); + Object isRedirect = list.get(2); pigeonResult.setIsRedirect((Boolean) isRedirect); - Object hasGesture = map.get("hasGesture"); + Object hasGesture = list.get(3); pigeonResult.setHasGesture((Boolean) hasGesture); - Object method = map.get("method"); + Object method = list.get(4); pigeonResult.setMethod((String) method); - Object requestHeaders = map.get("requestHeaders"); + Object requestHeaders = list.get(5); pigeonResult.setRequestHeaders((Map) requestHeaders); return pigeonResult; } @@ -246,21 +329,21 @@ public static final class Builder { } @NonNull - Map toMap() { - Map toMapResult = new HashMap<>(); - toMapResult.put("errorCode", errorCode); - toMapResult.put("description", description); - return toMapResult; + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(errorCode); + toListResult.add(description); + return toListResult; } - static @NonNull WebResourceErrorData fromMap(@NonNull Map map) { + static @NonNull WebResourceErrorData fromList(@NonNull ArrayList list) { WebResourceErrorData pigeonResult = new WebResourceErrorData(); - Object errorCode = map.get("errorCode"); + Object errorCode = list.get(0); pigeonResult.setErrorCode( (errorCode == null) ? null : ((errorCode instanceof Integer) ? (Integer) errorCode : (Long) errorCode)); - Object description = map.get("description"); + Object description = list.get(1); pigeonResult.setDescription((String) description); return pigeonResult; } @@ -321,18 +404,18 @@ public static final class Builder { } @NonNull - Map toMap() { - Map toMapResult = new HashMap<>(); - toMapResult.put("x", x); - toMapResult.put("y", y); - return toMapResult; + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(x); + toListResult.add(y); + return toListResult; } - static @NonNull WebViewPoint fromMap(@NonNull Map map) { + static @NonNull WebViewPoint fromList(@NonNull ArrayList list) { WebViewPoint pigeonResult = new WebViewPoint(); - Object x = map.get("x"); + Object x = list.get(0); pigeonResult.setX((x == null) ? null : ((x instanceof Integer) ? (Integer) x : (Long) x)); - Object y = map.get("y"); + Object y = list.get(1); pigeonResult.setY((y == null) ? null : ((y instanceof Integer) ? (Integer) y : (Long) y)); return pigeonResult; } @@ -370,7 +453,7 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -379,9 +462,10 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { throw new NullPointerException("identifierArg unexpectedly null."); } api.dispose((identifierArg == null) ? null : identifierArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -448,25 +532,25 @@ static void setup(BinaryMessenger binaryMessenger, CookieManagerHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { Result resultCallback = new Result() { public void success(Boolean result) { - wrapped.put("result", result); + wrapped.add(0, result); reply.reply(wrapped); } public void error(Throwable error) { - wrapped.put("error", wrapError(error)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); } }; api.clearCookies(resultCallback); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(exception); + reply.reply(wrappedError); } }); } else { @@ -480,7 +564,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -493,9 +577,10 @@ public void error(Throwable error) { throw new NullPointerException("valueArg unexpectedly null."); } api.setCookie(urlArg, valueArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -515,7 +600,7 @@ private WebViewHostApiCodec() {} protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: - return WebViewPoint.fromMap((Map) readValue(buffer)); + return WebViewPoint.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -526,7 +611,7 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebViewPoint) { stream.write(128); - writeValue(stream, ((WebViewPoint) value).toMap()); + writeValue(stream, ((WebViewPoint) value).toList()); } else { super.writeValue(stream, value); } @@ -620,7 +705,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -635,9 +720,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), useHybridCompositionArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -652,7 +738,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -671,9 +757,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { dataArg, mimeTypeArg, encodingArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -690,7 +777,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -713,9 +800,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { mimeTypeArg, encodingArg, historyUrlArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -730,7 +818,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -750,9 +838,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { (instanceIdArg == null) ? null : instanceIdArg.longValue(), urlArg, headersArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -767,7 +856,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -785,9 +874,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } api.postUrl( (instanceIdArg == null) ? null : instanceIdArg.longValue(), urlArg, dataArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -802,7 +892,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -812,9 +902,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } String output = api.getUrl((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -829,7 +920,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -839,9 +930,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } Boolean output = api.canGoBack((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -856,7 +948,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -866,9 +958,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } Boolean output = api.canGoForward((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -883,7 +976,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -892,9 +985,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.goBack((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -909,7 +1003,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -918,9 +1012,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.goForward((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -935,7 +1030,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -944,9 +1039,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.reload((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -961,7 +1057,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -976,9 +1072,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { api.clearCache( (instanceIdArg == null) ? null : instanceIdArg.longValue(), includeDiskFilesArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -995,7 +1092,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1010,13 +1107,13 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Result resultCallback = new Result() { public void success(String result) { - wrapped.put("result", result); + wrapped.add(0, result); reply.reply(wrapped); } public void error(Throwable error) { - wrapped.put("error", wrapError(error)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); } }; @@ -1025,8 +1122,8 @@ public void error(Throwable error) { javascriptStringArg, resultCallback); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(exception); + reply.reply(wrappedError); } }); } else { @@ -1040,7 +1137,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1050,9 +1147,10 @@ public void error(Throwable error) { } String output = api.getTitle((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1067,7 +1165,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1087,9 +1185,10 @@ public void error(Throwable error) { (instanceIdArg == null) ? null : instanceIdArg.longValue(), (xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1104,7 +1203,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1124,9 +1223,10 @@ public void error(Throwable error) { (instanceIdArg == null) ? null : instanceIdArg.longValue(), (xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1141,7 +1241,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1151,9 +1251,10 @@ public void error(Throwable error) { } Long output = api.getScrollX((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1168,7 +1269,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1178,9 +1279,10 @@ public void error(Throwable error) { } Long output = api.getScrollY((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1195,7 +1297,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1206,9 +1308,10 @@ public void error(Throwable error) { WebViewPoint output = api.getScrollPosition( (instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1225,7 +1328,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1234,9 +1337,10 @@ public void error(Throwable error) { throw new NullPointerException("enabledArg unexpectedly null."); } api.setWebContentsDebuggingEnabled(enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1251,7 +1355,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1268,9 +1372,10 @@ public void error(Throwable error) { (webViewClientInstanceIdArg == null) ? null : webViewClientInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1287,7 +1392,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1305,9 +1410,10 @@ public void error(Throwable error) { (javaScriptChannelInstanceIdArg == null) ? null : javaScriptChannelInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1324,7 +1430,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1342,9 +1448,10 @@ public void error(Throwable error) { (javaScriptChannelInstanceIdArg == null) ? null : javaScriptChannelInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1361,7 +1468,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1373,9 +1480,10 @@ public void error(Throwable error) { api.setDownloadListener( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (listenerInstanceIdArg == null) ? null : listenerInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1392,7 +1500,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1404,9 +1512,10 @@ public void error(Throwable error) { api.setWebChromeClient( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (clientInstanceIdArg == null) ? null : clientInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1423,7 +1532,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1438,9 +1547,10 @@ public void error(Throwable error) { api.setBackgroundColor( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (colorArg == null) ? null : colorArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1493,7 +1603,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1508,9 +1618,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (webViewInstanceIdArg == null) ? null : webViewInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1527,7 +1638,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1541,9 +1652,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setDomStorageEnabled( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1560,7 +1672,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1574,9 +1686,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setJavaScriptCanOpenWindowsAutomatically( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1593,7 +1706,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1607,9 +1720,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setSupportMultipleWindows( (instanceIdArg == null) ? null : instanceIdArg.longValue(), supportArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1626,7 +1740,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1640,9 +1754,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setJavaScriptEnabled( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1659,7 +1774,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1671,9 +1786,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { api.setUserAgentString( (instanceIdArg == null) ? null : instanceIdArg.longValue(), userAgentStringArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1690,7 +1806,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1704,9 +1820,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setMediaPlaybackRequiresUserGesture( (instanceIdArg == null) ? null : instanceIdArg.longValue(), requireArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1723,7 +1840,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1737,9 +1854,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setSupportZoom( (instanceIdArg == null) ? null : instanceIdArg.longValue(), supportArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1756,7 +1874,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1770,9 +1888,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setLoadWithOverviewMode( (instanceIdArg == null) ? null : instanceIdArg.longValue(), overviewArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1789,7 +1908,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1803,9 +1922,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setUseWideViewPort( (instanceIdArg == null) ? null : instanceIdArg.longValue(), useArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1822,7 +1942,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1836,9 +1956,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setDisplayZoomControls( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1855,7 +1976,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1869,9 +1990,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setBuiltInZoomControls( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1888,7 +2010,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1902,9 +2024,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setAllowFileAccess( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1934,7 +2057,7 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1948,9 +2071,10 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) } api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), channelNameArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2013,7 +2137,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2022,9 +2146,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2041,7 +2166,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2055,9 +2180,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { } api.setSynchronousReturnValueForShouldOverrideUrlLoading( (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2077,10 +2203,10 @@ private WebViewClientFlutterApiCodec() {} protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: - return WebResourceErrorData.fromMap((Map) readValue(buffer)); + return WebResourceErrorData.fromList((ArrayList) readValue(buffer)); case (byte) 129: - return WebResourceRequestData.fromMap((Map) readValue(buffer)); + return WebResourceRequestData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -2091,10 +2217,10 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebResourceErrorData) { stream.write(128); - writeValue(stream, ((WebResourceErrorData) value).toMap()); + writeValue(stream, ((WebResourceErrorData) value).toList()); } else if (value instanceof WebResourceRequestData) { stream.write(129); - writeValue(stream, ((WebResourceRequestData) value).toMap()); + writeValue(stream, ((WebResourceRequestData) value).toList()); } else { super.writeValue(stream, value); } @@ -2247,7 +2373,7 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2256,9 +2382,10 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2315,6 +2442,9 @@ public void onDownloadStart( public interface WebChromeClientHostApi { void create(@NonNull Long instanceId); + void setSynchronousReturnValueForOnShowFileChooser( + @NonNull Long instanceId, @NonNull Boolean value); + /** The codec used by WebChromeClientHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); @@ -2331,7 +2461,7 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2340,9 +2470,44 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + Boolean valueArg = (Boolean) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + api.setSynchronousReturnValueForOnShowFileChooser( + (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2376,7 +2541,7 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2385,9 +2550,10 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap throw new NullPointerException("pathArg unexpectedly null."); } List output = api.list(pathArg); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2404,7 +2570,7 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2413,9 +2579,10 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap throw new NullPointerException("nameArg unexpectedly null."); } String output = api.getAssetFilePathByName(nameArg); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2457,6 +2624,26 @@ public void onProgressChanged( callback.reply(null); }); } + + public void onShowFileChooser( + @NonNull Long instanceIdArg, + @NonNull Long webViewInstanceIdArg, + @NonNull Long paramsInstanceIdArg, + Reply> callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser", + getCodec()); + channel.send( + new ArrayList( + Arrays.asList(instanceIdArg, webViewInstanceIdArg, paramsInstanceIdArg)), + channelReply -> { + @SuppressWarnings("ConstantConditions") + List output = (List) channelReply; + callback.reply(output); + }); + } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebStorageHostApi { @@ -2479,7 +2666,7 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2488,9 +2675,10 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2505,7 +2693,7 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2514,9 +2702,10 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.deleteAllData((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2527,14 +2716,84 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { } } + private static class FileChooserParamsFlutterApiCodec extends StandardMessageCodec { + public static final FileChooserParamsFlutterApiCodec INSTANCE = + new FileChooserParamsFlutterApiCodec(); + + private FileChooserParamsFlutterApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return FileChooserModeEnumData.fromList((ArrayList) readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof FileChooserModeEnumData) { + stream.write(128); + writeValue(stream, ((FileChooserModeEnumData) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** + * Handles callbacks methods for the native Java FileChooserParams class. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. + * + *

Generated class from Pigeon that represents Flutter messages that can be called from Java. + */ + public static class FileChooserParamsFlutterApi { + private final BinaryMessenger binaryMessenger; + + public FileChooserParamsFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + /** The codec used by FileChooserParamsFlutterApi. */ + static MessageCodec getCodec() { + return FileChooserParamsFlutterApiCodec.INSTANCE; + } + + public void create( + @NonNull Long instanceIdArg, + @NonNull Boolean isCaptureEnabledArg, + @NonNull List acceptTypesArg, + @NonNull FileChooserModeEnumData modeArg, + @Nullable String filenameHintArg, + Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.FileChooserParamsFlutterApi.create", getCodec()); + channel.send( + new ArrayList( + Arrays.asList( + instanceIdArg, isCaptureEnabledArg, acceptTypesArg, modeArg, filenameHintArg)), + channelReply -> { + callback.reply(null); + }); + } + } + @NonNull - private static Map wrapError(@NonNull Throwable exception) { - Map errorMap = new HashMap<>(); - errorMap.put("message", exception.toString()); - errorMap.put("code", exception.getClass().getSimpleName()); - errorMap.put( - "details", + private static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList<>(3); + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); - return errorMap; + return errorList; } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java index fefd577ee9b5..55775a914c56 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java @@ -6,6 +6,7 @@ import android.os.Handler; import android.os.Looper; +import android.util.Log; import androidx.annotation.Nullable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; @@ -35,6 +36,8 @@ public class InstanceManager { // 0 <= n < 2^16. private static final long MIN_HOST_CREATED_IDENTIFIER = 65536; private static final long CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL = 30000; + private static final String TAG = "InstanceManager"; + private static final String CLOSED_WARNING = "Method was called while the manager was closed."; /** Interface for listening when a weak reference of an instance is removed from the manager. */ public interface FinalizationListener { @@ -79,11 +82,15 @@ private InstanceManager(FinalizationListener finalizationListener) { * * @param identifier the identifier paired to an instance. * @param the expected return type. - * @return the removed instance if the manager contains the given identifier, otherwise null. + * @return the removed instance if the manager contains the given identifier, otherwise null if + * the manager doesn't contain the value or the manager is closed. */ @Nullable public T remove(long identifier) { - assertManagerIsNotClosed(); + if (isClosed()) { + Log.w(TAG, CLOSED_WARNING); + return null; + } return (T) strongInstances.remove(identifier); } @@ -95,11 +102,14 @@ public T remove(long identifier) { * * @param instance an instance that may be stored in the manager. * @return the identifier associated with `instance` if the manager contains the value, otherwise - * null. + * null if the manager doesn't contain the value or the manager is closed. */ @Nullable public Long getIdentifierForStrongReference(Object instance) { - assertManagerIsNotClosed(); + if (isClosed()) { + Log.w(TAG, CLOSED_WARNING); + return null; + } final Long identifier = identifiers.get(instance); if (identifier != null) { strongInstances.put(identifier, instance); @@ -114,11 +124,16 @@ public Long getIdentifierForStrongReference(Object instance) { * The Dart InstanceManager is considered the source of truth and has the capability to overwrite * stored pairs in response to hot restarts. * + *

If the manager is closed, the addition is ignored. + * * @param instance the instance to be stored. * @param identifier the identifier to be paired with instance. This value must be >= 0. */ public void addDartCreatedInstance(Object instance, long identifier) { - assertManagerIsNotClosed(); + if (isClosed()) { + Log.w(TAG, CLOSED_WARNING); + return; + } addInstance(instance, identifier); } @@ -126,10 +141,13 @@ public void addDartCreatedInstance(Object instance, long identifier) { * Adds a new instance that was instantiated from the host platform. * * @param instance the instance to be stored. - * @return the unique identifier stored with instance. + * @return the unique identifier stored with instance. If the manager is closed, returns -1. */ public long addHostCreatedInstance(Object instance) { - assertManagerIsNotClosed(); + if (isClosed()) { + Log.w(TAG, CLOSED_WARNING); + return -1; + } final long identifier = nextIdentifier++; addInstance(instance, identifier); return identifier; @@ -141,11 +159,14 @@ public long addHostCreatedInstance(Object instance) { * @param identifier the identifier paired to an instance. * @param the expected return type. * @return the instance associated with `identifier` if the manager contains the value, otherwise - * null. + * null if the manager doesn't contain the value or the manager is closed. */ @Nullable public T getInstance(long identifier) { - assertManagerIsNotClosed(); + if (isClosed()) { + Log.w(TAG, CLOSED_WARNING); + return null; + } final WeakReference instance = (WeakReference) weakInstances.get(identifier); if (instance != null) { return instance.get(); @@ -157,22 +178,38 @@ public T getInstance(long identifier) { * Returns whether this manager contains the given `instance`. * * @param instance the instance whose presence in this manager is to be tested. - * @return whether this manager contains the given `instance`. + * @return whether this manager contains the given `instance`. If the manager is closed, returns + * `false`. */ public boolean containsInstance(Object instance) { - assertManagerIsNotClosed(); + if (isClosed()) { + Log.w(TAG, CLOSED_WARNING); + return false; + } return identifiers.containsKey(instance); } /** * Closes the manager and releases resources. * - *

Calling a method after calling this one will throw an {@link AssertionError}. This method - * excluded. + *

Methods called after this one will be ignored and log a warning. */ public void close() { handler.removeCallbacks(this::releaseAllFinalizedInstances); isClosed = true; + identifiers.clear(); + weakInstances.clear(); + strongInstances.clear(); + weakReferencesToIdentifiers.clear(); + } + + /** + * Whether the manager has released resources and is not longer usable. + * + *

See {@link #close()}. + */ + public boolean isClosed() { + return isClosed; } private void releaseAllFinalizedInstances() { @@ -199,10 +236,4 @@ private void addInstance(Object instance, long identifier) { weakReferencesToIdentifiers.put(weakReference, identifier); strongInstances.put(identifier, instance); } - - private void assertManagerIsNotClosed() { - if (isClosed) { - throw new AssertionError("Manager has already been closed."); - } - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java index 978e5232657d..9cbf65b4c613 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java @@ -27,6 +27,10 @@ public JavaObjectHostApiImpl(InstanceManager instanceManager) { @Override public void dispose(@NonNull Long identifier) { + final Object instance = instanceManager.getInstance(identifier); + if (instance instanceof WebViewHostApiImpl.WebViewPlatformView) { + ((WebViewHostApiImpl.WebViewPlatformView) instance).destroy(); + } instanceManager.remove(identifier); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java index cf263e2a6d34..92f0e41905cc 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java @@ -4,10 +4,14 @@ package io.flutter.plugins.webviewflutter; +import android.os.Build; import android.webkit.WebChromeClient; import android.webkit.WebView; +import androidx.annotation.RequiresApi; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientFlutterApi; +import java.util.List; +import java.util.Objects; /** * Flutter Api implementation for {@link WebChromeClient}. @@ -15,6 +19,7 @@ *

Passes arguments of callbacks methods from a {@link WebChromeClient} to Dart. */ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { + private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; /** @@ -26,6 +31,7 @@ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { public WebChromeClientFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); + this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; } @@ -40,6 +46,27 @@ public void onProgressChanged( getIdentifierForClient(webChromeClient), webViewIdentifier, progress, callback); } + /** Passes arguments from {@link WebChromeClient#onShowFileChooser} to Dart. */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void onShowFileChooser( + WebChromeClient webChromeClient, + WebView webView, + WebChromeClient.FileChooserParams fileChooserParams, + Reply> callback) { + Long paramsInstanceId = instanceManager.getIdentifierForStrongReference(fileChooserParams); + if (paramsInstanceId == null) { + final FileChooserParamsFlutterApiImpl flutterApi = + new FileChooserParamsFlutterApiImpl(binaryMessenger, instanceManager); + paramsInstanceId = flutterApi.create(fileChooserParams, reply -> {}); + } + + onShowFileChooser( + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webChromeClient)), + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView)), + paramsInstanceId, + callback); + } + private long getIdentifierForClient(WebChromeClient webChromeClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java index 3fa4a2f9c298..a5825c0133ec 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java @@ -4,8 +4,10 @@ package io.flutter.plugins.webviewflutter; +import android.net.Uri; import android.os.Build; import android.os.Message; +import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; @@ -15,6 +17,7 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; +import java.util.Objects; /** * Host api implementation for {@link WebChromeClient}. @@ -31,6 +34,7 @@ public class WebChromeClientHostApiImpl implements WebChromeClientHostApi { */ public static class WebChromeClientImpl extends SecureWebChromeClient { private final WebChromeClientFlutterApiImpl flutterApi; + private boolean returnValueForOnShowFileChooser = false; /** * Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart. @@ -45,6 +49,36 @@ public WebChromeClientImpl(@NonNull WebChromeClientFlutterApiImpl flutterApi) { public void onProgressChanged(WebView view, int progress) { flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onShowFileChooser( + WebView webView, + ValueCallback filePathCallback, + FileChooserParams fileChooserParams) { + final boolean currentReturnValueForOnShowFileChooser = returnValueForOnShowFileChooser; + flutterApi.onShowFileChooser( + this, + webView, + fileChooserParams, + reply -> { + // The returned list of file paths can only be passed to `filePathCallback` if the + // `onShowFileChooser` method returned true. + if (currentReturnValueForOnShowFileChooser) { + final Uri[] filePaths = new Uri[reply.size()]; + for (int i = 0; i < reply.size(); i++) { + filePaths[i] = Uri.parse(reply.get(i)); + } + filePathCallback.onReceiveValue(filePaths); + } + }); + return currentReturnValueForOnShowFileChooser; + } + + /** Sets return value for {@link #onShowFileChooser}. */ + public void setReturnValueForOnShowFileChooser(boolean value) { + returnValueForOnShowFileChooser = value; + } } /** @@ -163,4 +197,12 @@ public void create(Long instanceId) { webChromeClientCreator.createWebChromeClient(flutterApi); instanceManager.addDartCreatedInstance(webChromeClient, instanceId); } + + @Override + public void setSynchronousReturnValueForOnShowFileChooser( + @NonNull Long instanceId, @NonNull Boolean value) { + final WebChromeClientImpl webChromeClient = + Objects.requireNonNull(instanceManager.getInstance(instanceId)); + webChromeClient.setReturnValueForOnShowFileChooser(value); + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index 0a044cc5ab7e..77d535b78aed 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -102,9 +102,7 @@ public View getView() { } @Override - public void dispose() { - destroy(); - } + public void dispose() {} @Override public void setWebViewClient(WebViewClient webViewClient) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java new file mode 100644 index 000000000000..3172ea4330c8 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.webkit.WebChromeClient.FileChooserParams; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Arrays; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class FileChooserParamsTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public FileChooserParams mockFileChooserParams; + + @Mock public BinaryMessenger mockBinaryMessenger; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.open(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.close(); + } + + @Test + public void flutterApiCreate() { + final FileChooserParamsFlutterApiImpl spyFlutterApi = + spy(new FileChooserParamsFlutterApiImpl(mockBinaryMessenger, instanceManager)); + + when(mockFileChooserParams.isCaptureEnabled()).thenReturn(true); + when(mockFileChooserParams.getAcceptTypes()).thenReturn(new String[] {"my", "list"}); + when(mockFileChooserParams.getMode()).thenReturn(FileChooserParams.MODE_OPEN_MULTIPLE); + when(mockFileChooserParams.getFilenameHint()).thenReturn("filenameHint"); + spyFlutterApi.create(mockFileChooserParams, reply -> {}); + + final long identifier = + Objects.requireNonNull( + instanceManager.getIdentifierForStrongReference(mockFileChooserParams)); + final ArgumentCaptor modeCaptor = + ArgumentCaptor.forClass(GeneratedAndroidWebView.FileChooserModeEnumData.class); + + verify(spyFlutterApi) + .create( + eq(identifier), + eq(true), + eq(Arrays.asList("my", "list")), + modeCaptor.capture(), + eq("filenameHint"), + any()); + assertEquals( + modeCaptor.getValue().getValue(), GeneratedAndroidWebView.FileChooserMode.OPEN_MULTIPLE); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java index 4731e2a4beb1..6a19c883548a 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java @@ -5,6 +5,7 @@ package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -59,4 +60,52 @@ public void remove() { instanceManager.close(); } + + @Test + public void removeReturnsNullWhenClosed() { + final Object object = new Object(); + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + instanceManager.addDartCreatedInstance(object, 0); + instanceManager.close(); + + assertNull(instanceManager.remove(0)); + } + + @Test + public void getIdentifierForStrongReferenceReturnsNullWhenClosed() { + final Object object = new Object(); + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + instanceManager.addDartCreatedInstance(object, 0); + instanceManager.close(); + + assertNull(instanceManager.getIdentifierForStrongReference(object)); + } + + @Test + public void addHostCreatedInstanceReturnsNegativeOneWhenClosed() { + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + instanceManager.close(); + + assertEquals(instanceManager.addHostCreatedInstance(new Object()), -1L); + } + + @Test + public void getInstanceReturnsNullWhenClosed() { + final Object object = new Object(); + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + instanceManager.addDartCreatedInstance(object, 0); + instanceManager.close(); + + assertNull(instanceManager.getInstance(0)); + } + + @Test + public void containsInstanceReturnsFalseWhenClosed() { + final Object object = new Object(); + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + instanceManager.addDartCreatedInstance(object, 0); + instanceManager.close(); + + assertFalse(instanceManager.containsInstance(object)); + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index c49d6c5d1142..1721ccdce8e4 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -281,4 +281,37 @@ public void defaultWebChromeClientDoesNotAttemptToCommunicateWithDart() { // This shouldn't throw an Exception. Objects.requireNonNull(webView.getWebChromeClient()).onProgressChanged(webView, 0); } + + @Test + public void disposeDoesNotCallDestroy() { + final boolean[] destroyCalled = {false}; + final WebViewPlatformView webView = + new WebViewPlatformView(mockContext, null, null) { + @Override + public void destroy() { + destroyCalled[0] = true; + } + }; + webView.dispose(); + + assertFalse(destroyCalled[0]); + } + + @Test + public void destroyWebViewWhenDisposedFromJavaObjectHostApi() { + final boolean[] destroyCalled = {false}; + final WebViewPlatformView webView = + new WebViewPlatformView(mockContext, null, null) { + @Override + public void destroy() { + destroyCalled[0] = true; + } + }; + + testInstanceManager.addDartCreatedInstance(webView, 0); + final JavaObjectHostApiImpl javaObjectHostApi = new JavaObjectHostApiImpl(testInstanceManager); + javaObjectHostApi.dispose(0L); + + assertTrue(destroyCalled[0]); + } } 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/legacy/webview_flutter_test.dart index 57d33998a0d2..cbec6b767952 100644 --- 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/legacy/webview_flutter_test.dart @@ -985,6 +985,11 @@ Future main() async { final Map viewportRectRelativeToViewport = jsonDecode(viewportRectJSON) as Map; + num getDomRectComponent( + Map rectAsJson, String component) { + return rectAsJson[component]! as num; + } + // Check that the input is originally outside of the viewport. final String initialInputClientRectJSON = @@ -994,8 +999,9 @@ Future main() async { jsonDecode(initialInputClientRectJSON) as Map; expect( - initialInputClientRectRelativeToViewport['bottom'] <= - viewportRectRelativeToViewport['bottom'], + getDomRectComponent( + initialInputClientRectRelativeToViewport, 'bottom') <= + getDomRectComponent(viewportRectRelativeToViewport, 'bottom'), isFalse); await controller.runJavascript('inputEl.focus()'); @@ -1009,21 +1015,22 @@ Future main() async { jsonDecode(lastInputClientRectJSON) as Map; expect( - lastInputClientRectRelativeToViewport['top'] >= - viewportRectRelativeToViewport['top'], + getDomRectComponent(lastInputClientRectRelativeToViewport, 'top') >= + getDomRectComponent(viewportRectRelativeToViewport, 'top'), isTrue); expect( - lastInputClientRectRelativeToViewport['bottom'] <= - viewportRectRelativeToViewport['bottom'], + getDomRectComponent( + lastInputClientRectRelativeToViewport, 'bottom') <= + getDomRectComponent(viewportRectRelativeToViewport, 'bottom'), isTrue); expect( - lastInputClientRectRelativeToViewport['left'] >= - viewportRectRelativeToViewport['left'], + getDomRectComponent(lastInputClientRectRelativeToViewport, 'left') >= + getDomRectComponent(viewportRectRelativeToViewport, 'left'), isTrue); expect( - lastInputClientRectRelativeToViewport['right'] <= - viewportRectRelativeToViewport['right'], + getDomRectComponent(lastInputClientRectRelativeToViewport, 'right') <= + getDomRectComponent(viewportRectRelativeToViewport, 'right'), isTrue); }); }); @@ -1550,7 +1557,7 @@ class CopyableObjectWithCallback with Copyable { class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( - withWeakRefenceTo( + withWeakReferenceTo( this, (WeakReference weakReference) { return () { 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 dcd1dead700d..af144e55efba 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 @@ -17,6 +17,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter_android/src/android_webview.dart' as android; +import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; import 'package:webview_flutter_android/src/instance_manager.dart'; import 'package:webview_flutter_android/src/weak_reference_utils.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; @@ -58,15 +60,13 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -96,6 +96,79 @@ Future main() async { expect(gcIdentifier, 0); }, timeout: const Timeout(Duration(seconds: 10))); + testWidgets( + 'WebView is released by garbage collection', + (WidgetTester tester) async { + final Completer webViewGCCompleter = Completer(); + + late final InstanceManager instanceManager; + instanceManager = + InstanceManager(onWeakReferenceRemoved: (int identifier) { + final Copyable instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + if (instance is android.WebView && !webViewGCCompleter.isCompleted) { + webViewGCCompleter.complete(); + } + }); + + android.WebView.api = WebViewHostApiImpl( + instanceManager: instanceManager, + ); + android.WebSettings.api = WebSettingsHostApiImpl( + instanceManager: instanceManager, + ); + android.WebChromeClient.api = WebChromeClientHostApiImpl( + instanceManager: instanceManager, + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams( + instanceManager: instanceManager, + controller: PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams( + instanceManager: instanceManager, + controller: PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + await tester.pumpAndSettle(); + await expectLater(webViewGCCompleter.future, completes); + + android.WebView.api = WebViewHostApiImpl(); + android.WebSettings.api = WebSettingsHostApiImpl(); + android.WebChromeClient.api = WebChromeClientHostApiImpl(); + }, + timeout: const Timeout(Duration(seconds: 10)), + ); + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { final Completer pageFinished = Completer(); @@ -110,15 +183,13 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -151,15 +222,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoads.stream.firstWhere((String url) => url == headersUrl); @@ -195,15 +264,13 @@ Future main() async { 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -258,15 +325,13 @@ Future main() async { ..setUserAgent('Custom_User_Agent1') ..loadRequest(LoadRequestParams(uri: Uri.parse('about:blank'))); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -335,15 +400,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -369,15 +432,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -491,15 +552,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -525,15 +584,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -573,15 +630,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -1018,6 +1073,51 @@ Future main() async { expect(elementText, 'null'); }, ); + + testWidgets( + '`AndroidWebViewController` can be reused with a new `AndroidWebViewWidget`', + (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setPlatformNavigationDelegate(PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete())) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + pageLoaded = Completer(); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(primaryUrl)), + ); + await expectLater( + pageLoaded.future, + completes, + ); + }, + ); } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. @@ -1136,7 +1236,7 @@ class CopyableObjectWithCallback with Copyable { class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( - withWeakRefenceTo( + withWeakReferenceTo( this, (WeakReference weakReference) { return () { diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index fe6d723c058f..75f01b457b3a 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -164,9 +164,11 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } }, child: const Icon(Icons.favorite), ); @@ -319,25 +321,29 @@ class SampleMenu extends StatelessWidget { Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Cookies:'), - _getCookieList(cookies), - ], - ), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } } Future _onAddToCache(BuildContext context) async { await webViewController.runJavaScript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', ); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Added a test entry to cache.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } } Future _onListCache() { @@ -350,9 +356,11 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } } Future _onClearCookies(BuildContext context) async { @@ -361,9 +369,11 @@ class SampleMenu extends StatelessWidget { if (!hadCookies) { message = 'There are no cookies.'; } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } } Future _onNavigationDelegateExample() { @@ -462,10 +472,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } } }, ), @@ -475,10 +486,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No forward history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } } }, ), diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 0daacb07b13f..0fc0daf84118 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart deleted file mode 100644 index 51c62764fde4..000000000000 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'android_proxy.dart'; -import 'android_webview.dart' as android_webview; - -/// Signature for the `loadRequest` callback responsible for loading the [url] -/// after a navigation request has been approved. -typedef LoadRequestCallback = Future Function(LoadRequestParams params); - -/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. -@immutable -class AndroidWebResourceError extends WebResourceError { - /// Creates a new [AndroidWebResourceError]. - AndroidWebResourceError._({ - required super.errorCode, - required super.description, - super.isForMainFrame, - this.failingUrl, - }) : super( - errorType: _errorCodeToErrorType(errorCode), - ); - - /// Gets the URL for which the failing resource request was made. - final String? failingUrl; - - static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { - switch (errorCode) { - case android_webview.WebViewClient.errorAuthentication: - return WebResourceErrorType.authentication; - case android_webview.WebViewClient.errorBadUrl: - return WebResourceErrorType.badUrl; - case android_webview.WebViewClient.errorConnect: - return WebResourceErrorType.connect; - case android_webview.WebViewClient.errorFailedSslHandshake: - return WebResourceErrorType.failedSslHandshake; - case android_webview.WebViewClient.errorFile: - return WebResourceErrorType.file; - case android_webview.WebViewClient.errorFileNotFound: - return WebResourceErrorType.fileNotFound; - case android_webview.WebViewClient.errorHostLookup: - return WebResourceErrorType.hostLookup; - case android_webview.WebViewClient.errorIO: - return WebResourceErrorType.io; - case android_webview.WebViewClient.errorProxyAuthentication: - return WebResourceErrorType.proxyAuthentication; - case android_webview.WebViewClient.errorRedirectLoop: - return WebResourceErrorType.redirectLoop; - case android_webview.WebViewClient.errorTimeout: - return WebResourceErrorType.timeout; - case android_webview.WebViewClient.errorTooManyRequests: - return WebResourceErrorType.tooManyRequests; - case android_webview.WebViewClient.errorUnknown: - return WebResourceErrorType.unknown; - case android_webview.WebViewClient.errorUnsafeResource: - return WebResourceErrorType.unsafeResource; - case android_webview.WebViewClient.errorUnsupportedAuthScheme: - return WebResourceErrorType.unsupportedAuthScheme; - case android_webview.WebViewClient.errorUnsupportedScheme: - return WebResourceErrorType.unsupportedScheme; - } - - throw ArgumentError( - 'Could not find a WebResourceErrorType for errorCode: $errorCode', - ); - } -} - -/// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. -/// -/// When adding additional fields make sure they can be null or have a default -/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for -/// more information. -@immutable -class AndroidNavigationDelegateCreationParams - extends PlatformNavigationDelegateCreationParams { - /// Creates a new [AndroidNavigationDelegateCreationParams] instance. - const AndroidNavigationDelegateCreationParams._({ - @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), - }) : super(); - - /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams]. - factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( - // Recommended placeholder to prevent being broken by platform interface. - // ignore: avoid_unused_constructor_parameters - PlatformNavigationDelegateCreationParams params, { - @visibleForTesting - AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), - }) { - return AndroidNavigationDelegateCreationParams._( - androidWebViewProxy: androidWebViewProxy, - ); - } - - /// Handles constructing objects and calling static methods for the Android WebView - /// native library. - @visibleForTesting - final AndroidWebViewProxy androidWebViewProxy; -} - -/// A place to register callback methods responsible to handle navigation events -/// triggered by the [android_webview.WebView]. -class AndroidNavigationDelegate extends PlatformNavigationDelegate { - /// Creates a new [AndroidNavigationkDelegate]. - AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params) - : super.implementation(params is AndroidNavigationDelegateCreationParams - ? params - : AndroidNavigationDelegateCreationParams - .fromPlatformNavigationDelegateCreationParams(params)) { - final WeakReference weakThis = - WeakReference(this); - - _webChromeClient = (this.params as AndroidNavigationDelegateCreationParams) - .androidWebViewProxy - .createAndroidWebChromeClient( - onProgressChanged: (android_webview.WebView webView, int progress) { - if (weakThis.target?._onProgress != null) { - weakThis.target!._onProgress!(progress); - } - }); - - _webViewClient = (this.params as AndroidNavigationDelegateCreationParams) - .androidWebViewProxy - .createAndroidWebViewClient( - onPageFinished: (android_webview.WebView webView, String url) { - if (weakThis.target?._onPageFinished != null) { - weakThis.target!._onPageFinished!(url); - } - }, - onPageStarted: (android_webview.WebView webView, String url) { - if (weakThis.target?._onPageStarted != null) { - weakThis.target!._onPageStarted!(url); - } - }, - onReceivedRequestError: ( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - android_webview.WebResourceError error, - ) { - if (weakThis.target?._onWebResourceError != null) { - weakThis.target!._onWebResourceError!(AndroidWebResourceError._( - errorCode: error.errorCode, - description: error.description, - failingUrl: request.url, - isForMainFrame: request.isForMainFrame, - )); - } - }, - onReceivedError: ( - android_webview.WebView webView, - int errorCode, - String description, - String failingUrl, - ) { - if (weakThis.target?._onWebResourceError != null) { - weakThis.target!._onWebResourceError!(AndroidWebResourceError._( - errorCode: errorCode, - description: description, - failingUrl: failingUrl, - isForMainFrame: true, - )); - } - }, - requestLoading: ( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - ) { - if (weakThis.target != null) { - weakThis.target!._handleNavigation( - request.url, - headers: request.requestHeaders, - isForMainFrame: request.isForMainFrame, - ); - } - }, - urlLoading: ( - android_webview.WebView webView, - String url, - ) { - if (weakThis.target != null) { - weakThis.target!._handleNavigation(url, isForMainFrame: true); - } - }, - ); - - _downloadListener = (this.params as AndroidNavigationDelegateCreationParams) - .androidWebViewProxy - .createDownloadListener( - onDownloadStart: ( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - ) { - if (weakThis.target != null) { - weakThis.target?._handleNavigation(url, isForMainFrame: true); - } - }, - ); - } - - late final android_webview.WebChromeClient _webChromeClient; - - /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate]. - /// - /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`. - android_webview.WebChromeClient get androidWebChromeClient => - _webChromeClient; - - late final android_webview.WebViewClient _webViewClient; - - /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate]. - /// - /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`. - android_webview.WebViewClient get androidWebViewClient => _webViewClient; - - late final android_webview.DownloadListener _downloadListener; - - /// Gets the native [android_webview.DownloadListener] that is bridged by this [AndroidNavigationDelegate]. - /// - /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setDownloadListener`. - android_webview.DownloadListener get androidDownloadListener => - _downloadListener; - - PageEventCallback? _onPageFinished; - PageEventCallback? _onPageStarted; - ProgressCallback? _onProgress; - WebResourceErrorCallback? _onWebResourceError; - NavigationRequestCallback? _onNavigationRequest; - LoadRequestCallback? _onLoadRequest; - - void _handleNavigation( - String url, { - required bool isForMainFrame, - Map headers = const {}, - }) { - final LoadRequestCallback? onLoadRequest = _onLoadRequest; - final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; - - if (onNavigationRequest == null || onLoadRequest == null) { - return; - } - - final FutureOr returnValue = onNavigationRequest( - NavigationRequest( - url: url, - isMainFrame: isForMainFrame, - ), - ); - - if (returnValue is NavigationDecision && - returnValue == NavigationDecision.navigate) { - onLoadRequest(LoadRequestParams( - uri: Uri.parse(url), - headers: headers, - )); - } else if (returnValue is Future) { - returnValue.then((NavigationDecision shouldLoadUrl) { - if (shouldLoadUrl == NavigationDecision.navigate) { - onLoadRequest(LoadRequestParams( - uri: Uri.parse(url), - headers: headers, - )); - } - }); - } - } - - /// Invoked when loading the url after a navigation request is approved. - Future setOnLoadRequest( - LoadRequestCallback onLoadRequest, - ) async { - _onLoadRequest = onLoadRequest; - } - - @override - Future setOnNavigationRequest( - NavigationRequestCallback onNavigationRequest, - ) async { - _onNavigationRequest = onNavigationRequest; - _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading(true); - } - - @override - Future setOnPageStarted( - PageEventCallback onPageStarted, - ) async { - _onPageStarted = onPageStarted; - } - - @override - Future setOnPageFinished( - PageEventCallback onPageFinished, - ) async { - _onPageFinished = onPageFinished; - } - - @override - Future setOnProgress( - ProgressCallback onProgress, - ) async { - _onProgress = onProgress; - } - - @override - Future setOnWebResourceError( - WebResourceErrorCallback onWebResourceError, - ) async { - _onWebResourceError = onWebResourceError; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart index db247ee41d1c..9437e9dd3eb4 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart @@ -37,6 +37,11 @@ class AndroidWebViewProxy { final android_webview.WebChromeClient Function({ void Function(android_webview.WebView webView, int progress)? onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, }) createAndroidWebChromeClient; /// Constructs a [android_webview.WebViewClient]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 66f93dde1679..1ab30a9ea1fd 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -16,10 +16,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show BinaryMessenger; import 'package:flutter/widgets.dart' show AndroidViewSurface; -import 'android_webview.pigeon.dart'; +import 'android_webview.g.dart'; import 'android_webview_api_impls.dart'; import 'instance_manager.dart'; +export 'android_webview_api_impls.dart' show FileChooserMode; + /// Root of the Java class hierarchy. /// /// See https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html. @@ -871,7 +873,8 @@ class DownloadListener extends JavaObject { /// Handles JavaScript dialogs, favicons, titles, and the progress for [WebView]. class WebChromeClient extends JavaObject { /// Constructs a [WebChromeClient]. - WebChromeClient({this.onProgressChanged}) : super.detached() { + WebChromeClient({this.onProgressChanged, this.onShowFileChooser}) + : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } @@ -881,7 +884,10 @@ class WebChromeClient extends JavaObject { /// /// This should only be used by subclasses created by this library or to /// create copies. - WebChromeClient.detached({this.onProgressChanged}) : super.detached(); + WebChromeClient.detached({ + this.onProgressChanged, + this.onShowFileChooser, + }) : super.detached(); /// Pigeon Host Api implementation for [WebChromeClient]. @visibleForTesting @@ -890,9 +896,95 @@ class WebChromeClient extends JavaObject { /// Notify the host application that a file should be downloaded. final void Function(WebView webView, int progress)? onProgressChanged; + /// Indicates the client should show a file chooser. + /// + /// To handle the request for a file chooser with this callback, passing true + /// to [setSynchronousReturnValueForOnShowFileChooser] is required. Otherwise, + /// the returned list of strings will be ignored and the client will use the + /// default handling of a file chooser request. + /// + /// Only invoked on Android versions 21+. + final Future> Function( + WebView webView, + FileChooserParams params, + )? onShowFileChooser; + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onShowFileChooser(...)`. + /// + /// The Java method, `WebChromeClient.onShowFileChooser(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that all file chooser requests should be + /// handled by [onShowFileChooser] and the returned list of Strings will be + /// returned to the WebView. Otherwise, the client will use the default + /// handling and the returned value in [onShowFileChooser] will be ignored. + /// + /// Requires [onShowFileChooser] to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnShowFileChooser( + bool value, + ) { + if (value && onShowFileChooser == null) { + throw StateError( + 'Setting this to true requires `onShowFileChooser` to be nonnull.', + ); + } + return api.setSynchronousReturnValueForOnShowFileChooserFromInstance( + this, + value, + ); + } + @override WebChromeClient copy() { - return WebChromeClient.detached(onProgressChanged: onProgressChanged); + return WebChromeClient.detached( + onProgressChanged: onProgressChanged, + onShowFileChooser: onShowFileChooser, + ); + } +} + +/// Parameters received when a [WebChromeClient] should show a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +class FileChooserParams extends JavaObject { + /// Constructs a [FileChooserParams] without creating the associated Java + /// object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies. + FileChooserParams.detached({ + required this.isCaptureEnabled, + required this.acceptTypes, + required this.filenameHint, + required this.mode, + super.binaryMessenger, + super.instanceManager, + }) : super.detached(); + + /// Preference for a live media captured value (e.g. Camera, Microphone). + final bool isCaptureEnabled; + + /// A list of acceptable MIME types. + final List acceptTypes; + + /// The file name of a default selection if specified, or null. + final String? filenameHint; + + /// Mode of how to select files for a file chooser. + final FileChooserMode mode; + + @override + FileChooserParams copy() { + return FileChooserParams.detached( + isCaptureEnabled: isCaptureEnabled, + acceptTypes: acceptTypes, + filenameHint: filenameHint, + mode: mode, + ); } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart similarity index 68% rename from packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart rename to packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart index 5bdab16d6720..d3c306a10238 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v4.2.3), do not edit directly. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import import 'dart:async'; @@ -10,6 +10,48 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +/// Mode of how to select files for a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +enum FileChooserMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + open, + + /// Similar to [open] but allows multiple files to be selected. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + save, +} + +class FileChooserModeEnumData { + FileChooserModeEnumData({ + required this.value, + }); + + FileChooserMode value; + + Object encode() { + return [ + value.index, + ]; + } + + static FileChooserModeEnumData decode(Object result) { + result as List; + return FileChooserModeEnumData( + value: FileChooserMode.values[result[0]! as int], + ); + } +} + class WebResourceRequestData { WebResourceRequestData({ required this.url, @@ -21,33 +63,38 @@ class WebResourceRequestData { }); String url; + bool isForMainFrame; + bool? isRedirect; + bool hasGesture; + String method; + Map requestHeaders; Object encode() { - final Map pigeonMap = {}; - pigeonMap['url'] = url; - pigeonMap['isForMainFrame'] = isForMainFrame; - pigeonMap['isRedirect'] = isRedirect; - pigeonMap['hasGesture'] = hasGesture; - pigeonMap['method'] = method; - pigeonMap['requestHeaders'] = requestHeaders; - return pigeonMap; - } - - static WebResourceRequestData decode(Object message) { - final Map pigeonMap = message as Map; + return [ + url, + isForMainFrame, + isRedirect, + hasGesture, + method, + requestHeaders, + ]; + } + + static WebResourceRequestData decode(Object result) { + result as List; return WebResourceRequestData( - url: pigeonMap['url']! as String, - isForMainFrame: pigeonMap['isForMainFrame']! as bool, - isRedirect: pigeonMap['isRedirect'] as bool?, - hasGesture: pigeonMap['hasGesture']! as bool, - method: pigeonMap['method']! as String, - requestHeaders: (pigeonMap['requestHeaders'] as Map?)! - .cast(), + url: result[0]! as String, + isForMainFrame: result[1]! as bool, + isRedirect: result[2] as bool?, + hasGesture: result[3]! as bool, + method: result[4]! as String, + requestHeaders: + (result[5] as Map?)!.cast(), ); } } @@ -59,20 +106,21 @@ class WebResourceErrorData { }); int errorCode; + String description; Object encode() { - final Map pigeonMap = {}; - pigeonMap['errorCode'] = errorCode; - pigeonMap['description'] = description; - return pigeonMap; + return [ + errorCode, + description, + ]; } - static WebResourceErrorData decode(Object message) { - final Map pigeonMap = message as Map; + static WebResourceErrorData decode(Object result) { + result as List; return WebResourceErrorData( - errorCode: pigeonMap['errorCode']! as int, - description: pigeonMap['description']! as String, + errorCode: result[0]! as int, + description: result[1]! as String, ); } } @@ -84,20 +132,21 @@ class WebViewPoint { }); int x; + int y; Object encode() { - final Map pigeonMap = {}; - pigeonMap['x'] = x; - pigeonMap['y'] = y; - return pigeonMap; + return [ + x, + y, + ]; } - static WebViewPoint decode(Object message) { - final Map pigeonMap = message as Map; + static WebViewPoint decode(Object result) { + result as List; return WebViewPoint( - x: pigeonMap['x']! as int, - y: pigeonMap['y']! as int, + x: result[0]! as int, + y: result[1]! as int, ); } } @@ -121,20 +170,18 @@ class JavaObjectHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_identifier]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -149,6 +196,7 @@ abstract class JavaObjectFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); + static void setup(JavaObjectFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -187,28 +235,25 @@ class CookieManagerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CookieManagerHostApi.clearCookies', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as bool?)!; + return (replyList[0] as bool?)!; } } @@ -216,20 +261,18 @@ class CookieManagerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_url, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_url, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -275,21 +318,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_useHybridComposition]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -301,21 +342,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel.send( + final List? replyList = await channel.send( [arg_instanceId, arg_data, arg_mimeType, arg_encoding]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -332,26 +371,24 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel.send([ + final List? replyList = await channel.send([ arg_instanceId, arg_baseUrl, arg_data, arg_mimeType, arg_encoding, arg_historyUrl - ]) as Map?; - if (replyMap == null) { + ]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -363,21 +400,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_url, arg_headers]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -389,21 +424,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_url, arg_data]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_url, arg_data]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -414,23 +446,21 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { - return (replyMap['result'] as String?); + return (replyList[0] as String?); } } @@ -438,28 +468,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoBack', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as bool?)!; + return (replyList[0] as bool?)!; } } @@ -467,28 +495,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoForward', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as bool?)!; + return (replyList[0] as bool?)!; } } @@ -496,20 +522,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goBack', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -520,20 +544,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goForward', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -544,20 +566,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.reload', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -568,21 +588,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.clearCache', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_includeDiskFiles]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -594,24 +612,22 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.evaluateJavascript', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_javascriptString]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { - return (replyMap['result'] as String?); + return (replyList[0] as String?); } } @@ -619,23 +635,21 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getTitle', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { - return (replyMap['result'] as String?); + return (replyList[0] as String?); } } @@ -643,21 +657,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollTo', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_x, arg_y]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_x, arg_y]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -668,21 +679,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollBy', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_x, arg_y]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_x, arg_y]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -693,28 +701,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollX', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as int?)!; + return (replyList[0] as int?)!; } } @@ -722,28 +728,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollY', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as int?)!; + return (replyList[0] as int?)!; } } @@ -751,28 +755,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollPosition', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as WebViewPoint?)!; + return (replyList[0] as WebViewPoint?)!; } } @@ -781,20 +783,18 @@ class WebViewHostApi { 'dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -806,21 +806,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebViewClient', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel + final List? replyList = await channel .send([arg_instanceId, arg_webViewClientInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -832,21 +830,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel + final List? replyList = await channel .send([arg_instanceId, arg_javaScriptChannelInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -858,21 +854,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel + final List? replyList = await channel .send([arg_instanceId, arg_javaScriptChannelInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -884,21 +878,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setDownloadListener', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_listenerInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -910,21 +902,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebChromeClient', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_clientInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -935,20 +925,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_color]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_color]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -970,21 +958,19 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_webViewInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -995,20 +981,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_flag]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_flag]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1021,20 +1005,18 @@ class WebSettingsHostApi { 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_flag]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_flag]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1047,20 +1029,18 @@ class WebSettingsHostApi { 'dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_support]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_support]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1071,20 +1051,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_flag]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_flag]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1096,21 +1074,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_userAgentString]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_userAgentString]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1123,20 +1098,18 @@ class WebSettingsHostApi { 'dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_require]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_require]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1147,20 +1120,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_support]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_support]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1172,21 +1143,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_overview]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_overview]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1197,20 +1165,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_use]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_use]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1222,20 +1188,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1247,20 +1211,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1271,20 +1233,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1306,21 +1266,18 @@ class JavaScriptChannelHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaScriptChannelHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_channelName]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_channelName]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1332,6 +1289,7 @@ abstract class JavaScriptChannelFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void postMessage(int instanceId, String message); + static void setup(JavaScriptChannelFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1373,20 +1331,18 @@ class WebViewClientHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1399,20 +1355,18 @@ class WebViewClientHostApi { 'dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1454,14 +1408,20 @@ abstract class WebViewClientFlutterApi { static const MessageCodec codec = _WebViewClientFlutterApiCodec(); void onPageStarted(int instanceId, int webViewInstanceId, String url); + void onPageFinished(int instanceId, int webViewInstanceId, String url); + void onReceivedRequestError(int instanceId, int webViewInstanceId, WebResourceRequestData request, WebResourceErrorData error); + void onReceivedError(int instanceId, int webViewInstanceId, int errorCode, String description, String failingUrl); + void requestLoading( int instanceId, int webViewInstanceId, WebResourceRequestData request); + void urlLoading(int instanceId, int webViewInstanceId, String url); + static void setup(WebViewClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1647,20 +1607,18 @@ class DownloadListenerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.DownloadListenerHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1673,6 +1631,7 @@ abstract class DownloadListenerFlutterApi { void onDownloadStart(int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength); + static void setup(DownloadListenerFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1728,20 +1687,42 @@ class WebChromeClientHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future setSynchronousReturnValueForOnShowFileChooser( + int arg_instanceId, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1763,28 +1744,26 @@ class FlutterAssetManagerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_path]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_path]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as List?)!.cast(); + return (replyList[0] as List?)!.cast(); } } @@ -1793,28 +1772,26 @@ class FlutterAssetManagerHostApi { 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_name]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_name]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as String?)!; + return (replyList[0] as String?)!; } } } @@ -1823,6 +1800,10 @@ abstract class WebChromeClientFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void onProgressChanged(int instanceId, int webViewInstanceId, int progress); + + Future> onShowFileChooser( + int instanceId, int webViewInstanceId, int paramsInstanceId); + static void setup(WebChromeClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1852,6 +1833,33 @@ abstract class WebChromeClientFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null, expected non-null int.'); + final int? arg_webViewInstanceId = (args[1] as int?); + assert(arg_webViewInstanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null, expected non-null int.'); + final int? arg_paramsInstanceId = (args[2] as int?); + assert(arg_paramsInstanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null, expected non-null int.'); + final List output = await api.onShowFileChooser( + arg_instanceId!, arg_webViewInstanceId!, arg_paramsInstanceId!); + return output; + }); + } + } } } @@ -1869,20 +1877,18 @@ class WebStorageHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1893,23 +1899,92 @@ class WebStorageHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.deleteAllData', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; } } } + +class _FileChooserParamsFlutterApiCodec extends StandardMessageCodec { + const _FileChooserParamsFlutterApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is FileChooserModeEnumData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return FileChooserModeEnumData.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Handles callbacks methods for the native Java FileChooserParams class. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +abstract class FileChooserParamsFlutterApi { + static const MessageCodec codec = + _FileChooserParamsFlutterApiCodec(); + + void create(int instanceId, bool isCaptureEnabled, List acceptTypes, + FileChooserModeEnumData mode, String? filenameHint); + + static void setup(FileChooserParamsFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FileChooserParamsFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null int.'); + final bool? arg_isCaptureEnabled = (args[1] as bool?); + assert(arg_isCaptureEnabled != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null bool.'); + final List? arg_acceptTypes = + (args[2] as List?)?.cast(); + assert(arg_acceptTypes != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null List.'); + final FileChooserModeEnumData? arg_mode = + (args[3] as FileChooserModeEnumData?); + assert(arg_mode != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null FileChooserModeEnumData.'); + final String? arg_filenameHint = (args[4] as String?); + api.create(arg_instanceId!, arg_isCaptureEnabled!, arg_acceptTypes!, + arg_mode!, arg_filenameHint); + return; + }); + } + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 8aa5f7d9dab4..127a2fa58ef8 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -10,9 +10,11 @@ import 'dart:ui'; import 'package:flutter/services.dart' show BinaryMessenger; import 'android_webview.dart'; -import 'android_webview.pigeon.dart'; +import 'android_webview.g.dart'; import 'instance_manager.dart'; +export 'android_webview.g.dart' show FileChooserMode; + /// Converts [WebResourceRequestData] to [WebResourceRequest] WebResourceRequest _toWebResourceRequest(WebResourceRequestData data) { return WebResourceRequest( @@ -42,6 +44,7 @@ class AndroidWebViewFlutterApis { WebViewClientFlutterApiImpl? webViewClientFlutterApi, WebChromeClientFlutterApiImpl? webChromeClientFlutterApi, JavaScriptChannelFlutterApiImpl? javaScriptChannelFlutterApi, + FileChooserParamsFlutterApiImpl? fileChooserParamsFlutterApi, }) { this.javaObjectFlutterApi = javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); @@ -53,6 +56,8 @@ class AndroidWebViewFlutterApis { webChromeClientFlutterApi ?? WebChromeClientFlutterApiImpl(); this.javaScriptChannelFlutterApi = javaScriptChannelFlutterApi ?? JavaScriptChannelFlutterApiImpl(); + this.fileChooserParamsFlutterApi = + fileChooserParamsFlutterApi ?? FileChooserParamsFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -77,6 +82,9 @@ class AndroidWebViewFlutterApis { /// Flutter Api for [JavaScriptChannel]. late final JavaScriptChannelFlutterApiImpl javaScriptChannelFlutterApi; + /// Flutter Api for [FileChooserParams]. + late final FileChooserParamsFlutterApiImpl fileChooserParamsFlutterApi; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -85,6 +93,7 @@ class AndroidWebViewFlutterApis { WebViewClientFlutterApi.setup(webViewClientFlutterApi); WebChromeClientFlutterApi.setup(webChromeClientFlutterApi); JavaScriptChannelFlutterApi.setup(javaScriptChannelFlutterApi); + FileChooserParamsFlutterApi.setup(fileChooserParamsFlutterApi); _haveBeenSetUp = true; } } @@ -781,6 +790,17 @@ class WebChromeClientHostApiImpl extends WebChromeClientHostApi { return create(identifier); } } + + /// Helper method to convert instances ids to objects. + Future setSynchronousReturnValueForOnShowFileChooserFromInstance( + WebChromeClient instance, + bool value, + ) { + return setSynchronousReturnValueForOnShowFileChooser( + instanceManager.getIdentifier(instance)!, + value, + ); + } } /// Flutter api implementation for [DownloadListener]. @@ -810,6 +830,26 @@ class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { instance.onProgressChanged!(webViewInstance!, progress); } } + + @override + Future> onShowFileChooser( + int instanceId, + int webViewInstanceId, + int paramsInstanceId, + ) { + final WebChromeClient instance = + instanceManager.getInstanceWithWeakReference(instanceId)!; + if (instance.onShowFileChooser != null) { + return instance.onShowFileChooser!( + instanceManager.getInstanceWithWeakReference(webViewInstanceId)! + as WebView, + instanceManager.getInstanceWithWeakReference(paramsInstanceId)! + as FileChooserParams, + ); + } + + return Future>.value(const []); + } } /// Host api implementation for [WebStorage]. @@ -836,3 +876,32 @@ class WebStorageHostApiImpl extends WebStorageHostApi { return deleteAllData(instanceManager.getIdentifier(instance)!); } } + +/// Flutter api implementation for [FileChooserParams]. +class FileChooserParamsFlutterApiImpl extends FileChooserParamsFlutterApi { + /// Constructs a [FileChooserParamsFlutterApiImpl]. + FileChooserParamsFlutterApiImpl({InstanceManager? instanceManager}) + : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Maintains instances stored to communicate with java objects. + final InstanceManager instanceManager; + + @override + void create( + int instanceId, + bool isCaptureEnabled, + List acceptTypes, + FileChooserModeEnumData mode, + String? filenameHint, + ) { + instanceManager.addHostCreatedInstance( + FileChooserParams.detached( + isCaptureEnabled: isCaptureEnabled, + acceptTypes: acceptTypes.cast(), + mode: mode.value, + filenameHint: filenameHint, + ), + instanceId, + ); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 2e32705a83ea..fd287a515c65 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:async'; + // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; @@ -11,10 +15,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'android_navigation_delegate.dart'; import 'android_proxy.dart'; import 'android_webview.dart' as android_webview; -import 'android_webview.dart'; import 'instance_manager.dart'; import 'platform_views_service_proxy.dart'; import 'weak_reference_utils.dart'; @@ -76,6 +78,8 @@ class AndroidWebViewController extends PlatformWebViewController { _webView.settings.setUseWideViewPort(true); _webView.settings.setDisplayZoomControls(false); _webView.settings.setBuiltInZoomControls(true); + + _webView.setWebChromeClient(_webChromeClient); } AndroidWebViewControllerCreationParams get _androidWebViewParams => @@ -91,6 +95,32 @@ class AndroidWebViewController extends PlatformWebViewController { useHybridComposition: true, ); + late final android_webview.WebChromeClient _webChromeClient = + _androidWebViewParams.androidWebViewProxy.createAndroidWebChromeClient( + onProgressChanged: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (android_webview.WebView webView, int progress) { + if (weakReference.target?._currentNavigationDelegate?._onProgress != + null) { + weakReference + .target!._currentNavigationDelegate!._onProgress!(progress); + } + }; + }), + onShowFileChooser: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (android_webview.WebView webView, + android_webview.FileChooserParams params) async { + if (weakReference.target?._onShowFileSelectorCallback != null) { + return weakReference.target!._onShowFileSelectorCallback!( + FileSelectorParams._fromFileChooserParams(params), + ); + } + return []; + }; + }), + ); + /// The native [android_webview.FlutterAssetManager] allows managing assets. late final android_webview.FlutterAssetManager _flutterAssetManager = _androidWebViewParams.androidWebViewProxy.createFlutterAssetManager(); @@ -98,10 +128,10 @@ class AndroidWebViewController extends PlatformWebViewController { final Map _javaScriptChannelParams = {}; - // The keeps a reference to the current NavigationDelegate so that the - // callback methods remain reachable. - // ignore: unused_field - late AndroidNavigationDelegate _currentNavigationDelegate; + AndroidNavigationDelegate? _currentNavigationDelegate; + + Future> Function(FileSelectorParams)? + _onShowFileSelectorCallback; /// Whether to enable the platform's webview content debugging tools. /// @@ -175,11 +205,17 @@ class AndroidWebViewController extends PlatformWebViewController { case LoadRequestMethod.post: return _webView.postUrl( params.uri.toString(), params.body ?? Uint8List(0)); - default: - throw UnimplementedError( - 'This version of `AndroidWebViewController` currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.', - ); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of `AndroidWebViewController` currently has no ' + 'implementation for HTTP method ${params.method.serialize()} in ' + 'loadRequest.'); } @override @@ -213,7 +249,6 @@ class AndroidWebViewController extends PlatformWebViewController { _currentNavigationDelegate = handler; handler.setOnLoadRequest(loadRequest); _webView.setWebViewClient(handler.androidWebViewClient); - _webView.setWebChromeClient(handler.androidWebChromeClient); _webView.setDownloadListener(handler.androidDownloadListener); } @@ -309,6 +344,79 @@ class AndroidWebViewController extends PlatformWebViewController { Future setMediaPlaybackRequiresUserGesture(bool require) { return _webView.settings.setMediaPlaybackRequiresUserGesture(require); } + + /// Sets the callback that is invoked when the client should show a file + /// selector. + Future setOnShowFileSelector( + Future> Function(FileSelectorParams params)? + onShowFileSelector, + ) { + _onShowFileSelectorCallback = onShowFileSelector; + return _webChromeClient.setSynchronousReturnValueForOnShowFileChooser( + onShowFileSelector != null, + ); + } +} + +/// Mode of how to select files for a file chooser. +enum FileSelectorMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + open, + + /// Similar to [open] but allows multiple files to be selected. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + save, +} + +/// Parameters received when the `WebView` should show a file selector. +@immutable +class FileSelectorParams { + /// Constructs a [FileSelectorParams]. + const FileSelectorParams({ + required this.isCaptureEnabled, + required this.acceptTypes, + this.filenameHint, + required this.mode, + }); + + factory FileSelectorParams._fromFileChooserParams( + android_webview.FileChooserParams params, + ) { + final FileSelectorMode mode; + switch (params.mode) { + case android_webview.FileChooserMode.open: + mode = FileSelectorMode.open; + break; + case android_webview.FileChooserMode.openMultiple: + mode = FileSelectorMode.openMultiple; + break; + case android_webview.FileChooserMode.save: + mode = FileSelectorMode.save; + break; + } + + return FileSelectorParams( + isCaptureEnabled: params.isCaptureEnabled, + acceptTypes: params.acceptTypes, + mode: mode, + filenameHint: params.filenameHint, + ); + } + + /// Preference for a live media captured value (e.g. Camera, Microphone). + final bool isCaptureEnabled; + + /// A list of acceptable MIME types. + final List acceptTypes; + + /// The file name of a default selection if specified, or null. + final String? filenameHint; + + /// Mode of how to select files for a file selector. + final FileSelectorMode mode; } /// An implementation of [JavaScriptChannelParams] with the Android WebView API. @@ -325,7 +433,7 @@ class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { }) : assert(name.isNotEmpty), _javaScriptChannel = webViewProxy.createJavaScriptChannel( name, - postMessage: withWeakRefenceTo( + postMessage: withWeakReferenceTo( onMessageReceived, (WeakReference weakReference) { return ( @@ -374,7 +482,8 @@ class AndroidWebViewWidgetCreationParams @visibleForTesting InstanceManager? instanceManager, @visibleForTesting this.platformViewsServiceProxy = const PlatformViewsServiceProxy(), - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + }) : instanceManager = + instanceManager ?? android_webview.JavaObject.globalInstanceManager; /// Constructs a [WebKitWebViewWidgetCreationParams] using a /// [PlatformWebViewWidgetCreationParams]. @@ -488,3 +597,308 @@ class AndroidWebViewWidget extends PlatformWebViewWidget { } } } + +/// Signature for the `loadRequest` callback responsible for loading the [url] +/// after a navigation request has been approved. +typedef LoadRequestCallback = Future Function(LoadRequestParams params); + +/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. +@immutable +class AndroidWebResourceError extends WebResourceError { + /// Creates a new [AndroidWebResourceError]. + AndroidWebResourceError._({ + required super.errorCode, + required super.description, + super.isForMainFrame, + this.failingUrl, + }) : super( + errorType: _errorCodeToErrorType(errorCode), + ); + + /// Gets the URL for which the failing resource request was made. + final String? failingUrl; + + static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case android_webview.WebViewClient.errorAuthentication: + return WebResourceErrorType.authentication; + case android_webview.WebViewClient.errorBadUrl: + return WebResourceErrorType.badUrl; + case android_webview.WebViewClient.errorConnect: + return WebResourceErrorType.connect; + case android_webview.WebViewClient.errorFailedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case android_webview.WebViewClient.errorFile: + return WebResourceErrorType.file; + case android_webview.WebViewClient.errorFileNotFound: + return WebResourceErrorType.fileNotFound; + case android_webview.WebViewClient.errorHostLookup: + return WebResourceErrorType.hostLookup; + case android_webview.WebViewClient.errorIO: + return WebResourceErrorType.io; + case android_webview.WebViewClient.errorProxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case android_webview.WebViewClient.errorRedirectLoop: + return WebResourceErrorType.redirectLoop; + case android_webview.WebViewClient.errorTimeout: + return WebResourceErrorType.timeout; + case android_webview.WebViewClient.errorTooManyRequests: + return WebResourceErrorType.tooManyRequests; + case android_webview.WebViewClient.errorUnknown: + return WebResourceErrorType.unknown; + case android_webview.WebViewClient.errorUnsafeResource: + return WebResourceErrorType.unsafeResource; + case android_webview.WebViewClient.errorUnsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case android_webview.WebViewClient.errorUnsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } +} + +/// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for +/// more information. +@immutable +class AndroidNavigationDelegateCreationParams + extends PlatformNavigationDelegateCreationParams { + /// Creates a new [AndroidNavigationDelegateCreationParams] instance. + const AndroidNavigationDelegateCreationParams._({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + }) : super(); + + /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams]. + factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformNavigationDelegateCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + }) { + return AndroidNavigationDelegateCreationParams._( + androidWebViewProxy: androidWebViewProxy, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; +} + +/// A place to register callback methods responsible to handle navigation events +/// triggered by the [android_webview.WebView]. +class AndroidNavigationDelegate extends PlatformNavigationDelegate { + /// Creates a new [AndroidNavigationDelegate]. + AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params) + : super.implementation(params is AndroidNavigationDelegateCreationParams + ? params + : AndroidNavigationDelegateCreationParams + .fromPlatformNavigationDelegateCreationParams(params)) { + final WeakReference weakThis = + WeakReference(this); + + _webViewClient = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createAndroidWebViewClient( + onPageFinished: (android_webview.WebView webView, String url) { + if (weakThis.target?._onPageFinished != null) { + weakThis.target!._onPageFinished!(url); + } + }, + onPageStarted: (android_webview.WebView webView, String url) { + if (weakThis.target?._onPageStarted != null) { + weakThis.target!._onPageStarted!(url); + } + }, + onReceivedRequestError: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!(AndroidWebResourceError._( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + isForMainFrame: request.isForMainFrame, + )); + } + }, + onReceivedError: ( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + ) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!(AndroidWebResourceError._( + errorCode: errorCode, + description: description, + failingUrl: failingUrl, + isForMainFrame: true, + )); + } + }, + requestLoading: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + ) { + if (weakThis.target != null) { + weakThis.target!._handleNavigation( + request.url, + headers: request.requestHeaders, + isForMainFrame: request.isForMainFrame, + ); + } + }, + urlLoading: ( + android_webview.WebView webView, + String url, + ) { + if (weakThis.target != null) { + weakThis.target!._handleNavigation(url, isForMainFrame: true); + } + }, + ); + + _downloadListener = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createDownloadListener( + onDownloadStart: ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + if (weakThis.target != null) { + weakThis.target?._handleNavigation(url, isForMainFrame: true); + } + }, + ); + } + + AndroidNavigationDelegateCreationParams get _androidParams => + params as AndroidNavigationDelegateCreationParams; + + late final android_webview.WebChromeClient _webChromeClient = + _androidParams.androidWebViewProxy.createAndroidWebChromeClient(); + + /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`. + @Deprecated( + 'This value is not used by `AndroidWebViewController` and has no effect on the `WebView`.', + ) + android_webview.WebChromeClient get androidWebChromeClient => + _webChromeClient; + + late final android_webview.WebViewClient _webViewClient; + + /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`. + android_webview.WebViewClient get androidWebViewClient => _webViewClient; + + late final android_webview.DownloadListener _downloadListener; + + /// Gets the native [android_webview.DownloadListener] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setDownloadListener`. + android_webview.DownloadListener get androidDownloadListener => + _downloadListener; + + PageEventCallback? _onPageFinished; + PageEventCallback? _onPageStarted; + ProgressCallback? _onProgress; + WebResourceErrorCallback? _onWebResourceError; + NavigationRequestCallback? _onNavigationRequest; + LoadRequestCallback? _onLoadRequest; + + void _handleNavigation( + String url, { + required bool isForMainFrame, + Map headers = const {}, + }) { + final LoadRequestCallback? onLoadRequest = _onLoadRequest; + final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; + + if (onNavigationRequest == null || onLoadRequest == null) { + return; + } + + final FutureOr returnValue = onNavigationRequest( + NavigationRequest( + url: url, + isMainFrame: isForMainFrame, + ), + ); + + if (returnValue is NavigationDecision && + returnValue == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams( + uri: Uri.parse(url), + headers: headers, + )); + } else if (returnValue is Future) { + returnValue.then((NavigationDecision shouldLoadUrl) { + if (shouldLoadUrl == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams( + uri: Uri.parse(url), + headers: headers, + )); + } + }); + } + } + + /// Invoked when loading the url after a navigation request is approved. + Future setOnLoadRequest( + LoadRequestCallback onLoadRequest, + ) async { + _onLoadRequest = onLoadRequest; + } + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async { + _onNavigationRequest = onNavigationRequest; + _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading(true); + } + + @override + Future setOnPageStarted( + PageEventCallback onPageStarted, + ) async { + _onPageStarted = onPageStarted; + } + + @override + Future setOnPageFinished( + PageEventCallback onPageFinished, + ) async { + _onPageFinished = onPageFinished; + } + + @override + Future setOnProgress( + ProgressCallback onProgress, + ) async { + _onProgress = onProgress; + } + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async { + _onWebResourceError = onWebResourceError; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart index 24581ebd24dc..7997f69d7eba 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart @@ -4,7 +4,6 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'android_navigation_delegate.dart'; import 'android_webview_controller.dart'; import 'android_webview_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart index fc1028e7af15..cd4ba820cf4c 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart @@ -134,7 +134,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { final Map _javaScriptChannels = {}; - late final android_webview.WebViewClient _webViewClient = withWeakRefenceTo( + late final android_webview.WebViewClient _webViewClient = withWeakReferenceTo( this, (WeakReference weakReference) { return webViewProxy.createWebViewClient( onPageStarted: (_, String url) { @@ -213,7 +213,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting late final android_webview.DownloadListener downloadListener = android_webview.DownloadListener( - onDownloadStart: withWeakRefenceTo( + onDownloadStart: withWeakReferenceTo( this, (WeakReference weakReference) { return ( @@ -236,7 +236,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting late final android_webview.WebChromeClient webChromeClient = android_webview.WebChromeClient( - onProgressChanged: withWeakRefenceTo( + onProgressChanged: withWeakReferenceTo( this, (WeakReference weakReference) { return (_, int progress) { @@ -320,11 +320,17 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { case WebViewRequestMethod.post: return webView.postUrl( request.uri.toString(), request.body ?? Uint8List(0)); - default: - throw UnimplementedError( - 'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.', - ); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of webview_android_widget currently has no ' + 'implementation for HTTP method ${request.method.serialize()} in ' + 'loadRequest.'); } @override @@ -574,7 +580,7 @@ class WebViewAndroidJavaScriptChannel super.channelName, this.javascriptChannelRegistry, ) : super( - postMessage: withWeakRefenceTo( + postMessage: withWeakReferenceTo( javascriptChannelRegistry, (WeakReference weakReference) { return (String message) { diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart index ad0c9ebf4f5c..fd3e3f0dc273 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart @@ -25,7 +25,7 @@ /// ), /// ); /// ``` -S withWeakRefenceTo( +S withWeakReferenceTo( T reference, S Function(WeakReference weakReference) onCreate, ) { diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart index faab71549995..95f835ed8a1d 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart @@ -4,7 +4,6 @@ library webview_flutter_android; -export 'src/android_navigation_delegate.dart'; export 'src/android_webview_controller.dart'; export 'src/android_webview_cookie_manager.dart'; export 'src/android_webview_platform.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index d3adac8ee4c4..7f4d362c9273 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -6,8 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( - dartOut: 'lib/src/android_webview.pigeon.dart', - dartTestOut: 'test/test_android_webview.pigeon.dart', + dartOut: 'lib/src/android_webview.g.dart', + dartTestOut: 'test/test_android_webview.g.dart', dartOptions: DartOptions(copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', @@ -26,6 +26,34 @@ import 'package:pigeon/pigeon.dart'; ), ), ) + +/// Mode of how to select files for a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +enum FileChooserMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + open, + + /// Similar to [open] but allows multiple files to be selected. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + save, +} + +// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 +class FileChooserModeEnumData { + late FileChooserMode value; +} + class WebResourceRequestData { WebResourceRequestData( this.url, @@ -262,6 +290,11 @@ abstract class DownloadListenerFlutterApi { @HostApi(dartHostTestHandler: 'TestWebChromeClientHostApi') abstract class WebChromeClientHostApi { void create(int instanceId); + + void setSynchronousReturnValueForOnShowFileChooser( + int instanceId, + bool value, + ); } @HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') @@ -274,6 +307,13 @@ abstract class FlutterAssetManagerHostApi { @FlutterApi() abstract class WebChromeClientFlutterApi { void onProgressChanged(int instanceId, int webViewInstanceId, int progress); + + @async + List onShowFileChooser( + int instanceId, + int webViewInstanceId, + int paramsInstanceId, + ); } @HostApi(dartHostTestHandler: 'TestWebStorageHostApi') @@ -282,3 +322,17 @@ abstract class WebStorageHostApi { void deleteAllData(int instanceId); } + +/// Handles callbacks methods for the native Java FileChooserParams class. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +@FlutterApi() +abstract class FileChooserParamsFlutterApi { + void create( + int instanceId, + bool isCaptureEnabled, + List acceptTypes, + FileChooserModeEnumData mode, + String? filenameHint, + ); +} diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 72bc2e43414a..d90844d9ce08 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.1.1 +version: 3.2.4 environment: sdk: ">=2.17.0 <3.0.0" @@ -29,4 +29,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.3.2 - pigeon: ^4.2.3 + pigeon: ^4.2.14 diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart index 26d4e686d389..dac7c69a84f3 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -346,22 +346,6 @@ void main() { isTrue); }); - test('onProgress', () { - final AndroidNavigationDelegate androidNavigationDelegate = - AndroidNavigationDelegate(_buildCreationParams()); - - late final int callbackProgress; - androidNavigationDelegate - .setOnProgress((int progress) => callbackProgress = progress); - - CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( - android_webview.WebView.detached(), - 42, - ); - - expect(callbackProgress, 42); - }); - test( 'onLoadRequest from onDownloadStart should not be called when navigationRequestCallback is not specified', () { @@ -495,6 +479,7 @@ class CapturingWebViewClient extends android_webview.WebViewClient { class CapturingWebChromeClient extends android_webview.WebChromeClient { CapturingWebChromeClient({ super.onProgressChanged, + super.onShowFileChooser, }) : super.detached() { lastCreatedDelegate = this; } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index 0e7842ab467d..03e71ec5d987 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -19,6 +19,7 @@ import 'package:webview_flutter_android/src/platform_views_service_proxy.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; +import 'android_navigation_delegate_test.dart'; import 'android_webview_controller_test.mocks.dart'; @GenerateNiceMocks(>[ @@ -44,7 +45,16 @@ void main() { AndroidWebViewController createControllerWithMocks({ android_webview.FlutterAssetManager? mockFlutterAssetManager, android_webview.JavaScriptChannel? mockJavaScriptChannel, - android_webview.WebChromeClient? mockWebChromeClient, + android_webview.WebChromeClient Function({ + void Function(android_webview.WebView webView, int progress)? + onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + })? + createWebChromeClient, android_webview.WebView? mockWebView, android_webview.WebViewClient? mockWebViewClient, android_webview.WebStorage? mockWebStorage, @@ -57,10 +67,17 @@ void main() { AndroidWebViewControllerCreationParams( androidWebStorage: mockWebStorage ?? MockWebStorage(), androidWebViewProxy: AndroidWebViewProxy( - createAndroidWebChromeClient: ( - {void Function(android_webview.WebView, int)? - onProgressChanged}) => - mockWebChromeClient ?? MockWebChromeClient(), + createAndroidWebChromeClient: createWebChromeClient ?? + ({ + void Function(android_webview.WebView, int)? + onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + }) => + MockWebChromeClient(), createAndroidWebView: ({required bool useHybridComposition}) => nonNullMockWebView, createAndroidWebViewClient: ({ @@ -486,10 +503,101 @@ void main() { await controller.setPlatformNavigationDelegate(mockNavigationDelegate); - verifyInOrder([ - mockWebView.setWebViewClient(mockWebViewClient), - mockWebView.setWebChromeClient(mockWebChromeClient), - ]); + verify(mockWebView.setWebViewClient(mockWebViewClient)); + verifyNever(mockWebView.setWebChromeClient(mockWebChromeClient)); + }); + + test('onProgress', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate( + AndroidNavigationDelegateCreationParams + .fromPlatformNavigationDelegateCreationParams( + const PlatformNavigationDelegateCreationParams(), + androidWebViewProxy: const AndroidWebViewProxy( + createAndroidWebViewClient: android_webview.WebViewClient.detached, + createAndroidWebChromeClient: + android_webview.WebChromeClient.detached, + createDownloadListener: android_webview.DownloadListener.detached, + ), + ), + ); + + late final int callbackProgress; + androidNavigationDelegate + .setOnProgress((int progress) => callbackProgress = progress); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: CapturingWebChromeClient.new, + ); + controller.setPlatformNavigationDelegate(androidNavigationDelegate); + + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + android_webview.WebView.detached(), + 42, + ); + + expect(callbackProgress, 42); + }); + + test('onProgress does not cause LateInitializationError', () { + // ignore: unused_local_variable + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: CapturingWebChromeClient.new, + ); + + // Should not cause LateInitializationError + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + android_webview.WebView.detached(), + 42, + ); + }); + + test('setOnShowFileSelector', () async { + late final Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + ) onShowFileChooserCallback; + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: ({ + dynamic onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + }) { + onShowFileChooserCallback = onShowFileChooser!; + return mockWebChromeClient; + }, + ); + + late final FileSelectorParams fileSelectorParams; + await controller.setOnShowFileSelector( + (FileSelectorParams params) async { + fileSelectorParams = params; + return []; + }, + ); + + verify( + mockWebChromeClient.setSynchronousReturnValueForOnShowFileChooser(true), + ); + + onShowFileChooserCallback( + android_webview.WebView.detached(), + android_webview.FileChooserParams.detached( + isCaptureEnabled: false, + acceptTypes: ['png'], + filenameHint: 'filenameHint', + mode: android_webview.FileChooserMode.open, + ), + ); + + expect(fileSelectorParams.isCaptureEnabled, isFalse); + expect(fileSelectorParams.acceptTypes, ['png']); + expect(fileSelectorParams.filenameHint, 'filenameHint'); + expect(fileSelectorParams.mode, FileSelectorMode.open); }); test('runJavaScript', () async { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart index 643f5ac964b1..01885caff54c 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -4,20 +4,18 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i9; -import 'dart:typed_data' as _i15; +import 'dart:typed_data' as _i14; import 'dart:ui' as _i4; -import 'package:flutter/foundation.dart' as _i12; -import 'package:flutter/gestures.dart' as _i13; -import 'package:flutter/material.dart' as _i14; +import 'package:flutter/foundation.dart' as _i11; +import 'package:flutter/gestures.dart' as _i12; +import 'package:flutter/material.dart' as _i13; import 'package:flutter/services.dart' as _i7; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_android/src/android_navigation_delegate.dart' - as _i8; -import 'package:webview_flutter_android/src/android_proxy.dart' as _i11; +import 'package:webview_flutter_android/src/android_proxy.dart' as _i10; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; import 'package:webview_flutter_android/src/android_webview_controller.dart' - as _i10; + as _i8; import 'package:webview_flutter_android/src/instance_manager.dart' as _i5; import 'package:webview_flutter_android/src/platform_views_service_proxy.dart' as _i6; @@ -349,7 +347,7 @@ class MockAndroidNavigationDelegate extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockAndroidWebViewController extends _i1.Mock - implements _i10.AndroidWebViewController { + implements _i8.AndroidWebViewController { @override _i3.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), @@ -649,13 +647,25 @@ class MockAndroidWebViewController extends _i1.Mock returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); + @override + _i9.Future setOnShowFileSelector( + _i9.Future> Function(_i8.FileSelectorParams)? + onShowFileSelectorCallback) => + (super.noSuchMethod( + Invocation.method( + #setOnShowFileSelector, + [onShowFileSelectorCallback], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); } /// A class which mocks [AndroidWebViewProxy]. /// /// See the documentation for Mockito's code generation for more information. class MockAndroidWebViewProxy extends _i1.Mock - implements _i11.AndroidWebViewProxy { + implements _i10.AndroidWebViewProxy { @override _i2.WebView Function({required bool useHybridComposition}) get createAndroidWebView => (super.noSuchMethod( @@ -672,40 +682,63 @@ class MockAndroidWebViewProxy extends _i1.Mock ), ) as _i2.WebView Function({required bool useHybridComposition})); @override - _i2.WebChromeClient Function( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged}) get createAndroidWebChromeClient => - (super.noSuchMethod( + _i2.WebChromeClient Function({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + }) get createAndroidWebChromeClient => (super.noSuchMethod( Invocation.getter(#createAndroidWebChromeClient), - returnValue: ( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged}) => + returnValue: ({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + }) => _FakeWebChromeClient_0( this, Invocation.getter(#createAndroidWebChromeClient), ), - returnValueForMissingStub: ( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged}) => + returnValueForMissingStub: ({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + }) => _FakeWebChromeClient_0( this, Invocation.getter(#createAndroidWebChromeClient), ), - ) as _i2.WebChromeClient Function( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged})); + ) as _i2.WebChromeClient Function({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + })); @override _i2.WebViewClient Function({ void Function( @@ -958,7 +991,7 @@ class MockAndroidWebViewProxy extends _i1.Mock /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockAndroidWebViewWidgetCreationParams extends _i1.Mock - implements _i10.AndroidWebViewWidgetCreationParams { + implements _i8.AndroidWebViewWidgetCreationParams { @override _i5.InstanceManager get instanceManager => (super.noSuchMethod( Invocation.getter(#instanceManager), @@ -1009,13 +1042,13 @@ class MockAndroidWebViewWidgetCreationParams extends _i1.Mock returnValueForMissingStub: _i4.TextDirection.rtl, ) as _i4.TextDirection); @override - Set<_i12.Factory<_i13.OneSequenceGestureRecognizer>> get gestureRecognizers => + Set<_i11.Factory<_i12.OneSequenceGestureRecognizer>> get gestureRecognizers => (super.noSuchMethod( Invocation.getter(#gestureRecognizers), - returnValue: <_i12.Factory<_i13.OneSequenceGestureRecognizer>>{}, + returnValue: <_i11.Factory<_i12.OneSequenceGestureRecognizer>>{}, returnValueForMissingStub: < - _i12.Factory<_i13.OneSequenceGestureRecognizer>>{}, - ) as Set<_i12.Factory<_i13.OneSequenceGestureRecognizer>>); + _i11.Factory<_i12.OneSequenceGestureRecognizer>>{}, + ) as Set<_i11.Factory<_i12.OneSequenceGestureRecognizer>>); } /// A class which mocks [ExpensiveAndroidViewController]. @@ -1162,7 +1195,7 @@ class MockExpensiveAndroidViewController extends _i1.Mock returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override - _i9.Future dispatchPointerEvent(_i14.PointerEvent? event) => + _i9.Future dispatchPointerEvent(_i13.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #dispatchPointerEvent, @@ -1514,7 +1547,7 @@ class MockSurfaceAndroidViewController extends _i1.Mock returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override - _i9.Future dispatchPointerEvent(_i14.PointerEvent? event) => + _i9.Future dispatchPointerEvent(_i13.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #dispatchPointerEvent, @@ -1548,6 +1581,16 @@ class MockSurfaceAndroidViewController extends _i1.Mock /// See the documentation for Mockito's code generation for more information. class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { @override + _i9.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [value], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( #copy, @@ -1793,7 +1836,7 @@ class MockWebView extends _i1.Mock implements _i2.WebView { @override _i9.Future postUrl( String? url, - _i15.Uint8List? data, + _i14.Uint8List? data, ) => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index 4e972fe7b98d..236d87da44eb 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -6,12 +6,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart'; -import 'package:webview_flutter_android/src/android_webview.pigeon.dart'; +import 'package:webview_flutter_android/src/android_webview.g.dart'; import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; import 'package:webview_flutter_android/src/instance_manager.dart'; import 'android_webview_test.mocks.dart'; -import 'test_android_webview.pigeon.dart'; +import 'test_android_webview.g.dart'; @GenerateMocks([ CookieManagerHostApi, @@ -831,10 +831,120 @@ void main() { expect(result, containsAllInOrder([mockWebView, 76])); }); + test('onShowFileChooser', () async { + late final List result; + when(mockWebChromeClient.onShowFileChooser).thenReturn( + (WebView webView, FileChooserParams params) { + result = [webView, params]; + return Future>.value(['fileOne', 'fileTwo']); + }, + ); + + final FileChooserParams params = FileChooserParams.detached( + isCaptureEnabled: false, + acceptTypes: [], + filenameHint: 'filenameHint', + mode: FileChooserMode.open, + ); + + instanceManager.addHostCreatedInstance(params, 3); + + await expectLater( + flutterApi.onShowFileChooser( + mockWebChromeClientInstanceId, + mockWebViewInstanceId, + 3, + ), + completion(['fileOne', 'fileTwo']), + ); + expect(result[0], mockWebView); + expect(result[1], params); + }); + + test('setSynchronousReturnValueForOnShowFileChooser', () { + final MockTestWebChromeClientHostApi mockHostApi = + MockTestWebChromeClientHostApi(); + TestWebChromeClientHostApi.setup(mockHostApi); + + WebChromeClient.api = + WebChromeClientHostApiImpl(instanceManager: instanceManager); + + final WebChromeClient webChromeClient = WebChromeClient.detached(); + instanceManager.addHostCreatedInstance(webChromeClient, 2); + + webChromeClient.setSynchronousReturnValueForOnShowFileChooser(false); + + verify( + mockHostApi.setSynchronousReturnValueForOnShowFileChooser(2, false), + ); + }); + + test( + 'setSynchronousReturnValueForOnShowFileChooser throws StateError when onShowFileChooser is null', + () { + final MockTestWebChromeClientHostApi mockHostApi = + MockTestWebChromeClientHostApi(); + TestWebChromeClientHostApi.setup(mockHostApi); + + WebChromeClient.api = + WebChromeClientHostApiImpl(instanceManager: instanceManager); + + final WebChromeClient clientWithNullCallback = + WebChromeClient.detached(); + instanceManager.addHostCreatedInstance(clientWithNullCallback, 2); + + expect( + () => clientWithNullCallback + .setSynchronousReturnValueForOnShowFileChooser(true), + throwsStateError, + ); + + final WebChromeClient clientWithNonnullCallback = + WebChromeClient.detached( + onShowFileChooser: (_, __) async => [], + ); + instanceManager.addHostCreatedInstance(clientWithNonnullCallback, 3); + + clientWithNonnullCallback + .setSynchronousReturnValueForOnShowFileChooser(true); + + verify( + mockHostApi.setSynchronousReturnValueForOnShowFileChooser(3, true), + ); + }); + test('copy', () { expect(WebChromeClient.detached().copy(), isA()); }); }); + + group('FileChooserParams', () { + test('FlutterApi create', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final FileChooserParamsFlutterApiImpl flutterApi = + FileChooserParamsFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.create( + 0, + false, + const ['my', 'list'], + FileChooserModeEnumData(value: FileChooserMode.openMultiple), + 'filenameHint', + ); + + final FileChooserParams instance = instanceManager + .getInstanceWithWeakReference(0)! as FileChooserParams; + expect(instance.isCaptureEnabled, false); + expect(instance.acceptTypes, const ['my', 'list']); + expect(instance.mode, FileChooserMode.openMultiple); + expect(instance.filenameHint, 'filenameHint'); + }); + }); }); group('CookieManager', () { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 81cdc9ef545f..0b5afbaf5b13 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -9,9 +9,9 @@ import 'dart:ui' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/src/android_webview.pigeon.dart' as _i3; +import 'package:webview_flutter_android/src/android_webview.g.dart' as _i3; -import 'test_android_webview.pigeon.dart' as _i6; +import 'test_android_webview.g.dart' as _i6; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -304,6 +304,21 @@ class MockTestWebChromeClientHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); + @override + void setSynchronousReturnValueForOnShowFileChooser( + int? instanceId, + bool? value, + ) => + super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [ + instanceId, + value, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebSettingsHostApi]. @@ -952,6 +967,16 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { _i1.throwOnMissingStub(this); } + @override + _i5.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart index 1657e1b0d525..196e7cc27093 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart @@ -21,9 +21,12 @@ void main() { (MethodCall call) async { log.add(call); if (call.method == 'resize') { + final Map arguments = + (call.arguments as Map) + .cast(); return { - 'width': call.arguments['width'], - 'height': call.arguments['height'], + 'width': arguments['width'], + 'height': arguments['height'], }; } }, diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart index 909607ac8a71..44cc18510909 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart @@ -16,7 +16,7 @@ import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../android_webview_test.mocks.dart' show MockTestWebViewHostApi; -import '../test_android_webview.pigeon.dart'; +import '../test_android_webview.g.dart'; import 'webview_android_widget_test.mocks.dart'; @GenerateMocks([ @@ -210,8 +210,10 @@ void main() { ), ); - final List javaScriptChannels = - verify(mockWebView.addJavaScriptChannel(captureAny)).captured; + final List javaScriptChannels = + verify(mockWebView.addJavaScriptChannel(captureAny)) + .captured + .cast(); expect(javaScriptChannels[0].channelName, 'a'); expect(javaScriptChannels[1].channelName, 'b'); }); @@ -656,8 +658,10 @@ void main() { await buildWidget(tester); await testController.addJavascriptChannels({'c', 'd'}); - final List javaScriptChannels = - verify(mockWebView.addJavaScriptChannel(captureAny)).captured; + final List javaScriptChannels = + verify(mockWebView.addJavaScriptChannel(captureAny)) + .captured + .cast(); expect(javaScriptChannels[0].channelName, 'c'); expect(javaScriptChannels[1].channelName, 'd'); }); @@ -667,8 +671,10 @@ void main() { await testController.addJavascriptChannels({'c', 'd'}); await testController.removeJavascriptChannels({'c', 'd'}); - final List javaScriptChannels = - verify(mockWebView.removeJavaScriptChannel(captureAny)).captured; + final List javaScriptChannels = + verify(mockWebView.removeJavaScriptChannel(captureAny)) + .captured + .cast(); expect(javaScriptChannels[0].channelName, 'c'); expect(javaScriptChannels[1].channelName, 'd'); }); diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart index 1f16c29aa953..03489ce5c1e0 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart @@ -763,6 +763,16 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { _i1.throwOnMissingStub(this); } + @override + _i5.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart similarity index 94% rename from packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart rename to packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart index bcfb453e85c4..56ba79a66622 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v4.2.3), do not edit directly. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_android/src/android_webview.pigeon.dart'; +import 'package:webview_flutter_android/src/android_webview.g.dart'; /// Handles methods calls to the native Java Object class. /// @@ -22,6 +22,7 @@ abstract class TestJavaObjectHostApi { static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); + static void setup(TestJavaObjectHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -39,7 +40,7 @@ abstract class TestJavaObjectHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); - return {}; + return []; }); } } @@ -74,33 +75,59 @@ abstract class TestWebViewHostApi { static const MessageCodec codec = _TestWebViewHostApiCodec(); void create(int instanceId, bool useHybridComposition); + void loadData( int instanceId, String data, String? mimeType, String? encoding); + void loadDataWithBaseUrl(int instanceId, String? baseUrl, String data, String? mimeType, String? encoding, String? historyUrl); + void loadUrl(int instanceId, String url, Map headers); + void postUrl(int instanceId, String url, Uint8List data); + String? getUrl(int instanceId); + bool canGoBack(int instanceId); + bool canGoForward(int instanceId); + void goBack(int instanceId); + void goForward(int instanceId); + void reload(int instanceId); + void clearCache(int instanceId, bool includeDiskFiles); + Future evaluateJavascript(int instanceId, String javascriptString); + String? getTitle(int instanceId); + void scrollTo(int instanceId, int x, int y); + void scrollBy(int instanceId, int x, int y); + int getScrollX(int instanceId); + int getScrollY(int instanceId); + WebViewPoint getScrollPosition(int instanceId); + void setWebContentsDebuggingEnabled(bool enabled); + void setWebViewClient(int instanceId, int webViewClientInstanceId); + void addJavaScriptChannel(int instanceId, int javaScriptChannelInstanceId); + void removeJavaScriptChannel(int instanceId, int javaScriptChannelInstanceId); + void setDownloadListener(int instanceId, int? listenerInstanceId); + void setWebChromeClient(int instanceId, int? clientInstanceId); + void setBackgroundColor(int instanceId, int color); + static void setup(TestWebViewHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -121,7 +148,7 @@ abstract class TestWebViewHostApi { assert(arg_useHybridComposition != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.create was null, expected non-null bool.'); api.create(arg_instanceId!, arg_useHybridComposition!); - return {}; + return []; }); } } @@ -145,7 +172,7 @@ abstract class TestWebViewHostApi { final String? arg_mimeType = (args[2] as String?); final String? arg_encoding = (args[3] as String?); api.loadData(arg_instanceId!, arg_data!, arg_mimeType, arg_encoding); - return {}; + return []; }); } } @@ -172,7 +199,7 @@ abstract class TestWebViewHostApi { final String? arg_historyUrl = (args[5] as String?); api.loadDataWithBaseUrl(arg_instanceId!, arg_baseUrl, arg_data!, arg_mimeType, arg_encoding, arg_historyUrl); - return {}; + return []; }); } } @@ -198,7 +225,7 @@ abstract class TestWebViewHostApi { assert(arg_headers != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl was null, expected non-null Map.'); api.loadUrl(arg_instanceId!, arg_url!, arg_headers!); - return {}; + return []; }); } } @@ -223,7 +250,7 @@ abstract class TestWebViewHostApi { assert(arg_data != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null Uint8List.'); api.postUrl(arg_instanceId!, arg_url!, arg_data!); - return {}; + return []; }); } } @@ -242,7 +269,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getUrl was null, expected non-null int.'); final String? output = api.getUrl(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -261,7 +288,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoBack was null, expected non-null int.'); final bool output = api.canGoBack(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -280,7 +307,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoForward was null, expected non-null int.'); final bool output = api.canGoForward(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -299,7 +326,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goBack was null, expected non-null int.'); api.goBack(arg_instanceId!); - return {}; + return []; }); } } @@ -318,7 +345,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goForward was null, expected non-null int.'); api.goForward(arg_instanceId!); - return {}; + return []; }); } } @@ -337,7 +364,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.reload was null, expected non-null int.'); api.reload(arg_instanceId!); - return {}; + return []; }); } } @@ -359,7 +386,7 @@ abstract class TestWebViewHostApi { assert(arg_includeDiskFiles != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache was null, expected non-null bool.'); api.clearCache(arg_instanceId!, arg_includeDiskFiles!); - return {}; + return []; }); } } @@ -382,7 +409,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript was null, expected non-null String.'); final String? output = await api.evaluateJavascript( arg_instanceId!, arg_javascriptString!); - return {'result': output}; + return [output]; }); } } @@ -401,7 +428,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getTitle was null, expected non-null int.'); final String? output = api.getTitle(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -426,7 +453,7 @@ abstract class TestWebViewHostApi { assert(arg_y != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null, expected non-null int.'); api.scrollTo(arg_instanceId!, arg_x!, arg_y!); - return {}; + return []; }); } } @@ -451,7 +478,7 @@ abstract class TestWebViewHostApi { assert(arg_y != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null, expected non-null int.'); api.scrollBy(arg_instanceId!, arg_x!, arg_y!); - return {}; + return []; }); } } @@ -470,7 +497,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollX was null, expected non-null int.'); final int output = api.getScrollX(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -489,7 +516,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollY was null, expected non-null int.'); final int output = api.getScrollY(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -508,7 +535,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollPosition was null, expected non-null int.'); final WebViewPoint output = api.getScrollPosition(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -528,7 +555,7 @@ abstract class TestWebViewHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled was null, expected non-null bool.'); api.setWebContentsDebuggingEnabled(arg_enabled!); - return {}; + return []; }); } } @@ -550,7 +577,7 @@ abstract class TestWebViewHostApi { assert(arg_webViewClientInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient was null, expected non-null int.'); api.setWebViewClient(arg_instanceId!, arg_webViewClientInstanceId!); - return {}; + return []; }); } } @@ -573,7 +600,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel was null, expected non-null int.'); api.addJavaScriptChannel( arg_instanceId!, arg_javaScriptChannelInstanceId!); - return {}; + return []; }); } } @@ -596,7 +623,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel was null, expected non-null int.'); api.removeJavaScriptChannel( arg_instanceId!, arg_javaScriptChannelInstanceId!); - return {}; + return []; }); } } @@ -616,7 +643,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.setDownloadListener was null, expected non-null int.'); final int? arg_listenerInstanceId = (args[1] as int?); api.setDownloadListener(arg_instanceId!, arg_listenerInstanceId); - return {}; + return []; }); } } @@ -636,7 +663,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebChromeClient was null, expected non-null int.'); final int? arg_clientInstanceId = (args[1] as int?); api.setWebChromeClient(arg_instanceId!, arg_clientInstanceId); - return {}; + return []; }); } } @@ -658,7 +685,7 @@ abstract class TestWebViewHostApi { assert(arg_color != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor was null, expected non-null int.'); api.setBackgroundColor(arg_instanceId!, arg_color!); - return {}; + return []; }); } } @@ -669,18 +696,31 @@ abstract class TestWebSettingsHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, int webViewInstanceId); + void setDomStorageEnabled(int instanceId, bool flag); + void setJavaScriptCanOpenWindowsAutomatically(int instanceId, bool flag); + void setSupportMultipleWindows(int instanceId, bool support); + void setJavaScriptEnabled(int instanceId, bool flag); + void setUserAgentString(int instanceId, String? userAgentString); + void setMediaPlaybackRequiresUserGesture(int instanceId, bool require); + void setSupportZoom(int instanceId, bool support); + void setLoadWithOverviewMode(int instanceId, bool overview); + void setUseWideViewPort(int instanceId, bool use); + void setDisplayZoomControls(int instanceId, bool enabled); + void setBuiltInZoomControls(int instanceId, bool enabled); + void setAllowFileAccess(int instanceId, bool enabled); + static void setup(TestWebSettingsHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -701,7 +741,7 @@ abstract class TestWebSettingsHostApi { assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!, arg_webViewInstanceId!); - return {}; + return []; }); } } @@ -723,7 +763,7 @@ abstract class TestWebSettingsHostApi { assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled was null, expected non-null bool.'); api.setDomStorageEnabled(arg_instanceId!, arg_flag!); - return {}; + return []; }); } } @@ -747,7 +787,7 @@ abstract class TestWebSettingsHostApi { 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null, expected non-null bool.'); api.setJavaScriptCanOpenWindowsAutomatically( arg_instanceId!, arg_flag!); - return {}; + return []; }); } } @@ -770,7 +810,7 @@ abstract class TestWebSettingsHostApi { assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows was null, expected non-null bool.'); api.setSupportMultipleWindows(arg_instanceId!, arg_support!); - return {}; + return []; }); } } @@ -792,7 +832,7 @@ abstract class TestWebSettingsHostApi { assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled was null, expected non-null bool.'); api.setJavaScriptEnabled(arg_instanceId!, arg_flag!); - return {}; + return []; }); } } @@ -812,7 +852,7 @@ abstract class TestWebSettingsHostApi { 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString was null, expected non-null int.'); final String? arg_userAgentString = (args[1] as String?); api.setUserAgentString(arg_instanceId!, arg_userAgentString); - return {}; + return []; }); } } @@ -836,7 +876,7 @@ abstract class TestWebSettingsHostApi { 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null, expected non-null bool.'); api.setMediaPlaybackRequiresUserGesture( arg_instanceId!, arg_require!); - return {}; + return []; }); } } @@ -858,7 +898,7 @@ abstract class TestWebSettingsHostApi { assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom was null, expected non-null bool.'); api.setSupportZoom(arg_instanceId!, arg_support!); - return {}; + return []; }); } } @@ -881,7 +921,7 @@ abstract class TestWebSettingsHostApi { assert(arg_overview != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode was null, expected non-null bool.'); api.setLoadWithOverviewMode(arg_instanceId!, arg_overview!); - return {}; + return []; }); } } @@ -903,7 +943,7 @@ abstract class TestWebSettingsHostApi { assert(arg_use != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort was null, expected non-null bool.'); api.setUseWideViewPort(arg_instanceId!, arg_use!); - return {}; + return []; }); } } @@ -925,7 +965,7 @@ abstract class TestWebSettingsHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls was null, expected non-null bool.'); api.setDisplayZoomControls(arg_instanceId!, arg_enabled!); - return {}; + return []; }); } } @@ -947,7 +987,7 @@ abstract class TestWebSettingsHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls was null, expected non-null bool.'); api.setBuiltInZoomControls(arg_instanceId!, arg_enabled!); - return {}; + return []; }); } } @@ -969,7 +1009,7 @@ abstract class TestWebSettingsHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null bool.'); api.setAllowFileAccess(arg_instanceId!, arg_enabled!); - return {}; + return []; }); } } @@ -980,6 +1020,7 @@ abstract class TestJavaScriptChannelHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, String channelName); + static void setup(TestJavaScriptChannelHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1000,7 +1041,7 @@ abstract class TestJavaScriptChannelHostApi { assert(arg_channelName != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.create was null, expected non-null String.'); api.create(arg_instanceId!, arg_channelName!); - return {}; + return []; }); } } @@ -1011,8 +1052,10 @@ abstract class TestWebViewClientHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + void setSynchronousReturnValueForShouldOverrideUrlLoading( int instanceId, bool value); + static void setup(TestWebViewClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1030,7 +1073,7 @@ abstract class TestWebViewClientHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; }); } } @@ -1054,7 +1097,7 @@ abstract class TestWebViewClientHostApi { 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null, expected non-null bool.'); api.setSynchronousReturnValueForShouldOverrideUrlLoading( arg_instanceId!, arg_value!); - return {}; + return []; }); } } @@ -1065,6 +1108,7 @@ abstract class TestDownloadListenerHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + static void setup(TestDownloadListenerHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1082,7 +1126,7 @@ abstract class TestDownloadListenerHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.DownloadListenerHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; }); } } @@ -1093,6 +1137,10 @@ abstract class TestWebChromeClientHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + + void setSynchronousReturnValueForOnShowFileChooser( + int instanceId, bool value); + static void setup(TestWebChromeClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1110,7 +1158,31 @@ abstract class TestWebChromeClientHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser was null, expected non-null int.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser was null, expected non-null bool.'); + api.setSynchronousReturnValueForOnShowFileChooser( + arg_instanceId!, arg_value!); + return []; }); } } @@ -1121,7 +1193,9 @@ abstract class TestAssetManagerHostApi { static const MessageCodec codec = StandardMessageCodec(); List list(String path); + String getAssetFilePathByName(String name); + static void setup(TestAssetManagerHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1139,7 +1213,7 @@ abstract class TestAssetManagerHostApi { assert(arg_path != null, 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null, expected non-null String.'); final List output = api.list(arg_path!); - return {'result': output}; + return [output]; }); } } @@ -1159,7 +1233,7 @@ abstract class TestAssetManagerHostApi { assert(arg_name != null, 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null, expected non-null String.'); final String output = api.getAssetFilePathByName(arg_name!); - return {'result': output}; + return [output]; }); } } @@ -1170,7 +1244,9 @@ abstract class TestWebStorageHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + void deleteAllData(int instanceId); + static void setup(TestWebStorageHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1188,7 +1264,7 @@ abstract class TestWebStorageHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebStorageHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; }); } } @@ -1207,7 +1283,7 @@ abstract class TestWebStorageHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebStorageHostApi.deleteAllData was null, expected non-null int.'); api.deleteAllData(arg_instanceId!); - return {}; + return []; }); } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md index a4be504f3035..5c33fdbcea59 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 2.0.1 + +* Improves error message when a platform interface class is used before `WebViewPlatform.instance` has been set. + ## 2.0.0 * **Breaking Change**: Releases new interface. See [documentation](https://pub.dev/documentation/webview_flutter_platform_interface/2.0.0/) and [design doc](https://flutter.dev/go/webview_flutter_4_interface) diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_navigation_delegate.dart index 2df6cff73ab5..ec7af71eea51 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_navigation_delegate.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_navigation_delegate.dart @@ -31,6 +31,13 @@ abstract class PlatformNavigationDelegate extends PlatformInterface { /// Creates a new [PlatformNavigationDelegate] factory PlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params) { + assert( + WebViewPlatform.instance != null, + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + ); final PlatformNavigationDelegate callbackDelegate = WebViewPlatform.instance!.createPlatformNavigationDelegate(params); PlatformInterface.verify(callbackDelegate, _token); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_controller.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_controller.dart index 7bb259c274b1..bdeaa977d3dd 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_controller.dart @@ -21,6 +21,13 @@ abstract class PlatformWebViewController extends PlatformInterface { /// Creates a new [PlatformWebViewController] factory PlatformWebViewController( PlatformWebViewControllerCreationParams params) { + assert( + WebViewPlatform.instance != null, + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + ); final PlatformWebViewController webViewControllerDelegate = WebViewPlatform.instance!.createPlatformWebViewController(params); PlatformInterface.verify(webViewControllerDelegate, _token); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_cookie_manager.dart index 9e981c9022c6..a6740670e5c3 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_cookie_manager.dart @@ -19,6 +19,13 @@ abstract class PlatformWebViewCookieManager extends PlatformInterface { /// Creates a new [PlatformWebViewCookieManager] factory PlatformWebViewCookieManager( PlatformWebViewCookieManagerCreationParams params) { + assert( + WebViewPlatform.instance != null, + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + ); final PlatformWebViewCookieManager cookieManagerDelegate = WebViewPlatform.instance!.createPlatformCookieManager(params); PlatformInterface.verify(cookieManagerDelegate, _token); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_widget.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_widget.dart index 40334c650b3a..2e49c80d0a9c 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_widget.dart @@ -11,6 +11,13 @@ import 'webview_platform.dart'; abstract class PlatformWebViewWidget extends PlatformInterface { /// Creates a new [PlatformWebViewWidget] factory PlatformWebViewWidget(PlatformWebViewWidgetCreationParams params) { + assert( + WebViewPlatform.instance != null, + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + ); final PlatformWebViewWidget webViewWidgetDelegate = WebViewPlatform.instance!.createPlatformWebViewWidget(params); PlatformInterface.verify(webViewWidgetDelegate, _token); diff --git a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml index 3ba7a567398e..627b6098c302 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml @@ -4,11 +4,11 @@ repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutte issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview_flutter%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.0.0 +version: 2.0.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/webview_platform_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/webview_platform_test.dart index 34f84065e951..ec24dd7f5fa2 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/webview_platform_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/webview_platform_test.dart @@ -18,6 +18,69 @@ void main() { expect(WebViewPlatform.instance, isNull); }); + // This test can only run while `WebViewPlatform.instance` is still null. + test( + 'Interface classes throw assertion error when `WebViewPlatform.instance` is null', + () { + expect( + () => PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ), + throwsA(isA().having( + (AssertionError error) => error.message, + 'message', + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + )), + ); + + expect( + () => PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ), + throwsA(isA().having( + (AssertionError error) => error.message, + 'message', + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + )), + ); + + expect( + () => PlatformWebViewCookieManager( + const PlatformWebViewCookieManagerCreationParams(), + ), + throwsA(isA().having( + (AssertionError error) => error.message, + 'message', + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + )), + ); + + expect( + () => PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams( + controller: MockWebViewControllerDelegate(), + ), + ), + throwsA(isA().having( + (AssertionError error) => error.message, + 'message', + 'A platform implementation for `webview_flutter` has not been set. Please ' + 'ensure that an implementation of `WebViewPlatform` has been set to ' + '`WebViewPlatform.instance` before use. For unit testing, ' + '`WebViewPlatform.instance` can be set with your own test implementation.', + )), + ); + }); + test('Cannot be implemented with `implements`', () { expect(() { WebViewPlatform.instance = ImplementsWebViewPlatform(); diff --git a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md index 3b2c0212042a..028e03d715ff 100644 --- a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md @@ -1,3 +1,11 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + +## 0.2.1 + +* Adds auto registration of the `WebViewPlatform` implementation. + ## 0.2.0 * **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of diff --git a/packages/webview_flutter/webview_flutter_web/README.md b/packages/webview_flutter/webview_flutter_web/README.md index dcd1410f42c7..51a0223696d0 100644 --- a/packages/webview_flutter/webview_flutter_web/README.md +++ b/packages/webview_flutter/webview_flutter_web/README.md @@ -18,59 +18,6 @@ yet, so it currently requires extra setup to use: * [Add this package](https://pub.dev/packages/webview_flutter_web/install) as an explicit dependency of your project, in addition to depending on `webview_flutter`. -* Register `WebWebViewPlatform` as the `WebViewPlatform.instance` before creating a - `WebView`. See below for examples. -Once those steps below are complete, the APIs from `webview_flutter` listed +Once the step above is complete, the APIs from `webview_flutter` listed above can be used as normal on web. - -### Registering the implementation - -Before creating a `WebView` (for instance, at the start of `main`), you will -need to register the web implementation. - -#### Web-only project example - -```dart -... -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_web/webview_flutter_web.dart'; - -main() { - WebViewPlatform.instance = WebWebViewPlatform(); - ... -``` - -#### Multi-platform project example - -If your project supports platforms other than web, you will need to use a -conditional import to avoid directly including `webview_flutter_web.dart` on -non-web platforms. For example: - -`register_web_webview.dart`: -```dart -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_web/webview_flutter_web.dart'; - -void registerWebViewWebImplementation() { - WebViewPlatform.instance = WebWebViewPlatform(); -} -``` - -`register_web_webview_stub.dart`: -```dart -void registerWebViewWebImplementation() { - // No-op. -} -``` - -`main.dart`: -```dart -... -import 'register_web_webview_stub.dart' - if (dart.library.html) 'register_web.dart'; - -main() { - registerWebViewWebImplementation(); - ... -``` diff --git a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml index 782817eb7100..4685135acdf1 100644 --- a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart index 2624832514cd..a5afc2bc4189 100644 --- a/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart @@ -24,5 +24,7 @@ class WebWebViewPlatform extends WebViewPlatform { } /// Gets called when the plugin is registered. - static void registerWith(Registrar registrar) {} + static void registerWith(Registrar registrar) { + WebViewPlatform.instance = WebWebViewPlatform(); + } } diff --git a/packages/webview_flutter/webview_flutter_web/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/pubspec.yaml index 10f06801e805..66b67f4d67bd 100644 --- a/packages/webview_flutter/webview_flutter_web/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/pubspec.yaml @@ -2,11 +2,11 @@ name: webview_flutter_web description: A Flutter plugin that provides a WebView widget on web. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 0.2.0 +version: 0.2.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart b/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart new file mode 100644 index 000000000000..dbfaf22faa54 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_web/webview_flutter_web.dart'; + +void main() { + group('WebWebViewPlatform', () { + test('registerWith', () { + WebWebViewPlatform.registerWith(Registrar()); + expect(WebViewPlatform.instance, isA()); + }); + }); +} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 7ae38d347711..d8442c2c1f0e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,19 @@ +## 3.0.5 + +* Renames Pigeon output files. + +## 3.0.4 + +* Fixes bug that prevented the web view from being garbage collected. + +## 3.0.3 + +* Updates example code for `use_build_context_synchronously` lint. + +## 3.0.2 + +* Updates code for stricter lint checks. + ## 3.0.1 * Adds support for retrieving navigation type with internal class. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 946f27b5df83..16411b8140a5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -21,6 +21,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; import 'package:webview_flutter_wkwebview/src/common/weak_reference_utils.dart'; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; Future main() async { @@ -47,7 +48,7 @@ Future main() async { final String headersUrl = '$prefixUrl/headers'; testWidgets( - 'withWeakRefenceTo allows encapsulating class to be garbage collected', + 'withWeakReferenceTo allows encapsulating class to be garbage collected', (WidgetTester tester) async { final Completer gcCompleter = Completer(); final InstanceManager instanceManager = InstanceManager( @@ -68,21 +69,102 @@ Future main() async { expect(gcIdentifier, 0); }, timeout: const Timeout(Duration(seconds: 10))); + testWidgets( + 'WKWebView is released by garbage collection', + (WidgetTester tester) async { + final Completer webViewGCCompleter = Completer(); + + late final InstanceManager instanceManager; + instanceManager = + InstanceManager(onWeakReferenceRemoved: (int identifier) { + final Copyable instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + if (instance is WKWebView && !webViewGCCompleter.isCompleted) { + webViewGCCompleter.complete(); + } + }); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + WebKitWebViewWidgetCreationParams( + instanceManager: instanceManager, + controller: PlatformWebViewController( + WebKitWebViewControllerCreationParams( + instanceManager: instanceManager, + ), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + await tester.pumpWidget(Container()); + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + await expectLater(webViewGCCompleter.future, completes); + }, + timeout: const Timeout(Duration(seconds: 10)), + ); + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), - ); - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + ) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), - ); - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; await expectLater( controller.runJavaScriptReturningResult('1 + 1'), @@ -113,6 +195,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoads.stream.firstWhere((String url) => url == headersUrl); final String content = await controller.runJavaScriptReturningResult( @@ -147,6 +237,14 @@ Future main() async { 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageFinished.future; await controller.runJavaScript('Echo.postMessage("hello");'); @@ -184,6 +282,14 @@ Future main() async { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setUserAgent('Custom_User_Agent1'); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + final String customUserAgent2 = await _getUserAgent(controller); expect(customUserAgent2, 'Custom_User_Agent1'); }); @@ -250,6 +356,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; bool isPaused = @@ -274,6 +388,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; isPaused = @@ -447,6 +569,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; bool isPaused = @@ -471,6 +601,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; isPaused = @@ -509,6 +647,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native @@ -565,6 +711,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); @@ -599,8 +753,7 @@ Future main() async { '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { - final StreamController pageLoads = - StreamController.broadcast(); + Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), @@ -610,7 +763,7 @@ Future main() async { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) - ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent @@ -621,10 +774,20 @@ Future main() async { LoadRequestParams(uri: Uri.parse(blankPageEncoded)), ); - await pageLoads.stream.first; // Wait for initial page load. + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; - await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); @@ -633,7 +796,7 @@ Future main() async { final Completer errorCompleter = Completer(); - PlatformWebViewController( + final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -648,6 +811,14 @@ Future main() async { LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); @@ -660,7 +831,7 @@ Future main() async { Completer(); final Completer pageFinishCompleter = Completer(); - PlatformWebViewController( + final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -681,6 +852,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); @@ -706,7 +885,7 @@ Future main() async { Completer(); final Completer pageFinishCompleter = Completer(); - PlatformWebViewController( + final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -727,14 +906,21 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }, ); testWidgets('can block requests', (WidgetTester tester) async { - final StreamController pageLoads = - StreamController.broadcast(); + Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), @@ -744,7 +930,7 @@ Future main() async { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) - ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent @@ -753,22 +939,31 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); - await pageLoads.stream.first; // Wait for initial page load. + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller .runJavaScript('location.href = "https://www.youtube.com/"'); // There should never be any second page load, since our new URL is // blocked. Still wait for a potential page change for some time in order // to give the test a chance to fail. - await pageLoads.stream.first + await pageLoaded.future .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, isNot(contains('youtube.com'))); }); testWidgets('supports asynchronous decisions', (WidgetTester tester) async { - final StreamController pageLoads = - StreamController.broadcast(); + Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), @@ -778,7 +973,7 @@ Future main() async { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) - ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest( (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; @@ -790,10 +985,20 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); - await pageLoads.stream.first; // Wait for initial page load. + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller.runJavaScript('location.href = "$secondaryUrl"'); - await pageLoads.stream.first; // Wait for second page to load. + await pageLoaded.future; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); @@ -807,6 +1012,14 @@ Future main() async { ..setAllowsBackForwardNavigationGestures(true) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); @@ -824,6 +1037,15 @@ Future main() async { )..setOnPageFinished((_) => pageLoaded.complete())); await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); @@ -843,6 +1065,14 @@ Future main() async { )..setOnPageFinished((_) => pageLoaded.complete())) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index 84aced1b75e8..aef7ece0c2e3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -165,9 +165,11 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } }, child: const Icon(Icons.favorite), ); @@ -320,25 +322,29 @@ class SampleMenu extends StatelessWidget { Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Cookies:'), - _getCookieList(cookies), - ], - ), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } } Future _onAddToCache(BuildContext context) async { await webViewController.runJavaScript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', ); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Added a test entry to cache.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } } Future _onListCache() { @@ -351,9 +357,11 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } } Future _onClearCookies(BuildContext context) async { @@ -362,9 +370,11 @@ class SampleMenu extends StatelessWidget { if (!hadCookies) { message = 'There are no cookies.'; } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } } Future _onNavigationDelegateExample() { @@ -463,10 +473,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } } }, ), @@ -476,10 +487,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No forward history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } } }, ), diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index 7ccb302a843b..718eb282018b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart similarity index 100% rename from packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart rename to packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart index d2310e0a5df8..445e232bb0ac 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart @@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; -import '../common/web_kit.pigeon.dart'; +import '../common/web_kit.g.dart'; import 'foundation.dart'; Iterable diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart index cc901a4a7efb..4d10db96a291 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart @@ -353,9 +353,9 @@ class WebKitWebViewPlatformController extends WebViewPlatformController { // unsupported. This also goes for `null` and `undefined` on iOS 14+. For // example, when running a void function. For ease of use, this specific // error is ignored when no return value is expected. - if (exception.details is! NSError || - exception.details.code != - WKErrorCode.javaScriptResultTypeIsUnsupported) { + final Object? details = exception.details; + if (details is! NSError || + details.code != WKErrorCode.javaScriptResultTypeIsUnsupported) { rethrow; } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart index ae12a11820d8..4749c6afca3c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart @@ -11,7 +11,7 @@ import 'package:flutter/painting.dart' show Color; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; -import '../common/web_kit.pigeon.dart'; +import '../common/web_kit.g.dart'; import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; import 'ui_kit.dart'; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart index 97a3e0008f81..7cd29da3e716 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart @@ -6,11 +6,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; -import '../common/web_kit.pigeon.dart'; +import '../common/web_kit.g.dart'; import '../foundation/foundation.dart'; import 'web_kit.dart'; -export '../common/web_kit.pigeon.dart' show WKNavigationType; +export '../common/web_kit.g.dart' show WKNavigationType; Iterable _toWKWebsiteDataTypeEnumData( Iterable types) { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart index 2cdc7e269454..3e8d6796069b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'common/instance_manager.dart'; import 'foundation/foundation.dart'; import 'web_kit/web_kit.dart'; @@ -39,10 +40,13 @@ class WebKitProxy { Map change, )? observeValue, + InstanceManager? instanceManager, }) createWebView; /// Constructs a [WKWebViewConfiguration]. - final WKWebViewConfiguration Function() createWebViewConfiguration; + final WKWebViewConfiguration Function({ + InstanceManager? instanceManager, + }) createWebViewConfiguration; /// Constructs a [WKScriptMessageHandler]. final WKScriptMessageHandler Function({ @@ -72,7 +76,7 @@ class WebKitProxy { void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, }) createNavigationDelegate; - /// Contructs a [WKUIDelegate]. + /// Constructs a [WKUIDelegate]. final WKUIDelegate Function({ void Function( WKWebView webView, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart index 48219416e44e..02b5b73b5971 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart @@ -49,7 +49,12 @@ class WebKitWebViewControllerCreationParams PlaybackMediaTypes.video, }, this.allowsInlineMediaPlayback = false, - }) : _configuration = webKitProxy.createWebViewConfiguration() { + @visibleForTesting InstanceManager? instanceManager, + }) : _instanceManager = instanceManager ?? NSObject.globalInstanceManager { + _configuration = webKitProxy.createWebViewConfiguration( + instanceManager: _instanceManager, + ); + if (mediaTypesRequiringUserAction.isEmpty) { _configuration.setMediaTypesRequiringUserActionForPlayback( {WKAudiovisualMediaType.none}, @@ -79,13 +84,15 @@ class WebKitWebViewControllerCreationParams PlaybackMediaTypes.video, }, bool allowsInlineMediaPlayback = false, + @visibleForTesting InstanceManager? instanceManager, }) : this( webKitProxy: webKitProxy, mediaTypesRequiringUserAction: mediaTypesRequiringUserAction, allowsInlineMediaPlayback: allowsInlineMediaPlayback, + instanceManager: instanceManager, ); - final WKWebViewConfiguration _configuration; + late final WKWebViewConfiguration _configuration; /// Media types that require a user gesture to begin playing. /// @@ -102,6 +109,10 @@ class WebKitWebViewControllerCreationParams /// native library. @visibleForTesting final WebKitProxy webKitProxy; + + // Maintains instances used to communicate with the native objects they + // represent. + final InstanceManager _instanceManager; } /// An implementation of [PlatformWebViewController] with the WebKit api. @@ -122,12 +133,12 @@ class WebKitWebViewController extends PlatformWebViewController { } /// The WebKit WebView being controlled. - late final WKWebView _webView = withWeakRefenceTo(this, ( - WeakReference weakReference, - ) { - return _webKitParams.webKitProxy.createWebView( - _webKitParams._configuration, - observeValue: ( + late final WKWebView _webView = _webKitParams.webKitProxy.createWebView( + _webKitParams._configuration, + observeValue: withWeakRefenceTo(this, ( + WeakReference weakReference, + ) { + return ( String keyPath, NSObject object, Map change, @@ -139,9 +150,10 @@ class WebKitWebViewController extends PlatformWebViewController { change[NSKeyValueChangeKey.newValue]! as double; progressCallback((progress * 100).round()); } - }, - ); - }); + }; + }), + instanceManager: _webKitParams._instanceManager, + ); final Map _javaScriptChannelParams = {}; @@ -269,9 +281,9 @@ class WebKitWebViewController extends PlatformWebViewController { // unsupported. This also goes for `null` and `undefined` on iOS 14+. For // example, when running a void function. For ease of use, this specific // error is ignored when no return value is expected. - if (exception.details is! NSError || - exception.details.code != - WKErrorCode.javaScriptResultTypeIsUnsupported) { + final Object? details = exception.details; + if (details is! NSError || + details.code != WKErrorCode.javaScriptResultTypeIsUnsupported) { rethrow; } } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart index d32693ee5698..9b334c2411ff 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart @@ -6,8 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( - dartOut: 'lib/src/common/web_kit.pigeon.dart', - dartTestOut: 'test/src/common/test_web_kit.pigeon.dart', + dartOut: 'lib/src/common/web_kit.g.dart', + dartTestOut: 'test/src/common/test_web_kit.g.dart', dartOptions: DartOptions(copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 27a6a7863806..5c4df9922840 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.0.1 +version: 3.0.5 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart index da7ce9b18aef..7982be1c0353 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart @@ -137,13 +137,13 @@ void main() { (WidgetTester tester) async { await buildWidget(tester); - final dynamic onCreateWebView = verify( - mockWebViewWidgetProxy.createUIDelgate( + final void Function(WKWebView, WKWebViewConfiguration, WKNavigationAction) + onCreateWebView = verify(mockWebViewWidgetProxy.createUIDelgate( onCreateWebView: captureAnyNamed('onCreateWebView'))) - .captured - .single - as void Function( - WKWebView, WKWebViewConfiguration, WKNavigationAction); + .captured + .single + as void Function( + WKWebView, WKWebViewConfiguration, WKNavigationAction); const NSUrlRequest request = NSUrlRequest(url: 'https://google.com'); onCreateWebView( @@ -989,7 +989,7 @@ void main() { testWidgets('onPageStarted', (WidgetTester tester) async { await buildWidget(tester); - final dynamic didStartProvisionalNavigation = + final void Function(WKWebView, String) didStartProvisionalNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: @@ -1010,7 +1010,7 @@ void main() { testWidgets('onPageFinished', (WidgetTester tester) async { await buildWidget(tester); - final dynamic didFinishNavigation = + final void Function(WKWebView, String) didFinishNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: captureAnyNamed('didFinishNavigation'), didStartProvisionalNavigation: @@ -1032,7 +1032,7 @@ void main() { (WidgetTester tester) async { await buildWidget(tester); - final dynamic didFailNavigation = + final void Function(WKWebView, NSError) didFailNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: @@ -1069,7 +1069,7 @@ void main() { (WidgetTester tester) async { await buildWidget(tester); - final dynamic didFailProvisionalNavigation = + final void Function(WKWebView, NSError) didFailProvisionalNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: @@ -1110,7 +1110,7 @@ void main() { (WidgetTester tester) async { await buildWidget(tester); - final dynamic webViewWebContentProcessDidTerminate = + final void Function(WKWebView) webViewWebContentProcessDidTerminate = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: @@ -1142,7 +1142,8 @@ void main() { (WidgetTester tester) async { await buildWidget(tester, hasNavigationDelegate: true); - final dynamic decidePolicyForNavigationAction = + final Future Function( + WKWebView, WKNavigationAction) decidePolicyForNavigationAction = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: @@ -1191,15 +1192,13 @@ void main() { }, )); - final dynamic observeValue = verify( - mockWebViewWidgetProxy.createWebView(any, - observeValue: captureAnyNamed('observeValue'))) - .captured - .single as void Function( - String keyPath, - NSObject object, - Map change, - ); + final void Function(String, NSObject, Map) + observeValue = verify(mockWebViewWidgetProxy.createWebView(any, + observeValue: captureAnyNamed('observeValue'))) + .captured + .single + as void Function( + String, NSObject, Map); observeValue( 'estimatedProgress', @@ -1234,15 +1233,14 @@ void main() { await buildWidget(tester); await testController.addJavascriptChannels({'hello'}); - final dynamic didReceiveScriptMessage = verify( - mockWebViewWidgetProxy.createScriptMessageHandler( - didReceiveScriptMessage: - captureAnyNamed('didReceiveScriptMessage'))) - .captured - .single as void Function( - WKUserContentController userContentController, - WKScriptMessage message, - ); + final void Function(WKUserContentController, WKScriptMessage) + didReceiveScriptMessage = verify( + mockWebViewWidgetProxy.createScriptMessageHandler( + didReceiveScriptMessage: + captureAnyNamed('didReceiveScriptMessage'))) + .captured + .single + as void Function(WKUserContentController, WKScriptMessage); didReceiveScriptMessage( mockUserContentController, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart similarity index 99% rename from packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart rename to packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart index 73c1053f517d..5c31f63c3add 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart'; class _TestWKWebsiteDataStoreHostApiCodec extends StandardMessageCodec { const _TestWKWebsiteDataStoreHostApiCodec(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart index 87b659885b52..b9536208c716 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart @@ -8,11 +8,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation_api_impls.dart'; -import '../common/test_web_kit.pigeon.dart'; +import '../common/test_web_kit.g.dart'; import 'foundation_test.mocks.dart'; @GenerateMocks([ diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart index fe80a54ed9ac..d93198ed9d2f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart @@ -4,10 +4,9 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart' - as _i3; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3; -import '../common/test_web_kit.pigeon.dart' as _i2; +import '../common/test_web_kit.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart index f2250e1ac423..f6295668363f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart @@ -12,7 +12,7 @@ import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; -import '../common/test_web_kit.pigeon.dart'; +import '../common/test_web_kit.g.dart'; import 'ui_kit_test.mocks.dart'; @GenerateMocks([ diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart index 660c4485ab1b..6200b8dbcadf 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart @@ -6,10 +6,9 @@ import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart' - as _i3; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3; -import '../common/test_web_kit.pigeon.dart' as _i2; +import '../common/test_web_kit.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart index a2b456ee5898..dd007869f0e3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart @@ -9,12 +9,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit_api_impls.dart'; -import '../common/test_web_kit.pigeon.dart'; +import '../common/test_web_kit.g.dart'; import 'web_kit_test.mocks.dart'; @GenerateMocks([ @@ -116,13 +116,15 @@ void main() { completion(true), ); - final List typeData = + final List capturedArgs = verify(mockPlatformHostApi.removeDataOfTypes( instanceManager.getIdentifier(websiteDataStore), captureAny, 5.0, - )).captured.single.cast() - as List; + )).captured; + final List typeData = + (capturedArgs.single as List) + .cast(); expect(typeData.single.value, WKWebsiteDataTypeEnum.cookies); }); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart index a1a5bf224596..50e09560ed19 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart @@ -6,10 +6,9 @@ import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart' - as _i4; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i4; -import '../common/test_web_kit.pigeon.dart' as _i2; +import '../common/test_web_kit.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart index fc06db24f055..0360c13b052a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart @@ -13,6 +13,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; @@ -49,6 +50,7 @@ void main() { })? createMockWebView, MockWKWebViewConfiguration? mockWebViewConfiguration, + InstanceManager? instanceManager, }) { final MockWKWebViewConfiguration nonNullMockWebViewConfiguration = mockWebViewConfiguration ?? MockWKWebViewConfiguration(); @@ -57,7 +59,9 @@ void main() { final PlatformWebViewControllerCreationParams controllerCreationParams = WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => nonNullMockWebViewConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return nonNullMockWebViewConfiguration; + }, createWebView: ( _, { void Function( @@ -66,6 +70,7 @@ void main() { Map change, )? observeValue, + InstanceManager? instanceManager, }) { nonNullMockWebView = createMockWebView == null ? MockWKWebView() @@ -104,7 +109,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), allowsInlineMediaPlayback: true, ); @@ -120,7 +127,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), mediaTypesRequiringUserAction: const { PlaybackMediaTypes.video, @@ -143,7 +152,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), ); @@ -164,7 +175,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), mediaTypesRequiringUserAction: const {}, ); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart index 2e0d6e3e9af3..2a6434be4f03 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart @@ -19,7 +19,7 @@ void main() { group('WebKitWebViewWidget', () { testWidgets('build', (WidgetTester tester) async { - final InstanceManager instanceManager = InstanceManager( + final InstanceManager testInstanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); @@ -34,14 +34,17 @@ void main() { Map change, )? observeValue, + InstanceManager? instanceManager, }) { final WKWebView webView = WKWebView.detached( - instanceManager: instanceManager, + instanceManager: testInstanceManager, ); - instanceManager.addDartCreatedInstance(webView); + testInstanceManager.addDartCreatedInstance(webView); return webView; }, - createWebViewConfiguration: () => MockWKWebViewConfiguration(), + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return MockWKWebViewConfiguration(); + }, ), ), ); @@ -50,7 +53,7 @@ void main() { WebKitWebViewWidgetCreationParams( key: const Key('keyValue'), controller: controller, - instanceManager: instanceManager, + instanceManager: testInstanceManager, ), ); diff --git a/script/configs/exclude_all_plugins_app.yaml b/script/configs/exclude_all_packages_app.yaml similarity index 100% rename from script/configs/exclude_all_plugins_app.yaml rename to script/configs/exclude_all_packages_app.yaml diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index 072d661f8d99..3c4905ad7071 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -1,3 +1,24 @@ +## 0.13.4+1 + +* Makes `--packages-for-branch` detect any commit on `main` as being `main`, + so that it works with pinned checkouts (e.g., on LUCI). + +## 0.13.4 + +* Adds the ability to validate minimum supported Dart/Flutter versions in + `pubspec-check`. + +## 0.13.3 + +* Renames `podspecs` to `podspec-check`. The old name will continue to work. +* Adds validation of the Swift-in-Obj-C-projects workaround in the podspecs of + iOS plugin implementations that use Swift. + +## 0.13.2+1 + +* Replaces deprecated `flutter format` with `dart format` in `format` + implementation. + ## 0.13.2 * Falls back to other executables in PATH when `clang-format` does not run. diff --git a/script/tool/lib/src/common/git_version_finder.dart b/script/tool/lib/src/common/git_version_finder.dart index b135424827a6..3965ae0ace47 100644 --- a/script/tool/lib/src/common/git_version_finder.dart +++ b/script/tool/lib/src/common/git_version_finder.dart @@ -88,7 +88,8 @@ class GitVersionFinder { if (fileContent.trim().isEmpty) { return null; } - final String? versionString = loadYaml(fileContent)['version'] as String?; + final YamlMap fileYaml = loadYaml(fileContent) as YamlMap; + final String? versionString = fileYaml['version'] as String?; return versionString == null ? null : Version.parse(versionString); } diff --git a/script/tool/lib/src/common/package_command.dart b/script/tool/lib/src/common/package_command.dart index 0e83d03e9846..8a2bbfc40058 100644 --- a/script/tool/lib/src/common/package_command.dart +++ b/script/tool/lib/src/common/package_command.dart @@ -316,17 +316,28 @@ abstract class PackageCommand extends Command { } else if (getBoolArg(_packagesForBranchArg)) { final String? branch = await _getBranch(); if (branch == null) { - printError('Unabled to determine branch; --$_packagesForBranchArg can ' + printError('Unable to determine branch; --$_packagesForBranchArg can ' 'only be used in a git repository.'); throw ToolExit(exitInvalidArguments); } else { // Configure the change finder the correct mode for the branch. - final bool lastCommitOnly = branch == 'main' || branch == 'master'; + // Log the mode to make it easier to audit logs to see that the + // intended diff was used (or why). + final bool lastCommitOnly; + if (branch == 'main' || branch == 'master') { + print('--$_packagesForBranchArg: running on default branch.'); + lastCommitOnly = true; + } else if (await _isCheckoutFromBranch('main')) { + print( + '--$_packagesForBranchArg: running on a commit from default branch.'); + lastCommitOnly = true; + } else { + print('--$_packagesForBranchArg: running on branch "$branch".'); + lastCommitOnly = false; + } if (lastCommitOnly) { - // Log the mode to make it easier to audit logs to see that the - // intended diff was used. - print('--$_packagesForBranchArg: running on default branch; ' - 'using parent commit as the diff base.'); + print( + '--$_packagesForBranchArg: using parent commit as the diff base.'); changedFileFinder = GitVersionFinder(await gitDir, 'HEAD~'); } else { changedFileFinder = await retrieveVersionFinder(); @@ -522,6 +533,35 @@ abstract class PackageCommand extends Command { return packages; } + // Returns true if the current checkout is on an ancestor of [branch]. + // + // This is used because CI may check out a specific hash rather than a branch, + // in which case branch-name detection won't work. + Future _isCheckoutFromBranch(String branchName) async { + // The target branch may not exist locally; try some common remote names for + // the branch as well. + final List candidateBranchNames = [ + branchName, + 'origin/$branchName', + 'upstream/$branchName', + ]; + for (final String branch in candidateBranchNames) { + final io.ProcessResult result = await (await gitDir).runCommand( + ['merge-base', '--is-ancestor', 'HEAD', branch], + throwOnError: false); + if (result.exitCode == 0) { + return true; + } else if (result.exitCode == 1) { + // 1 indicates that the branch was successfully checked, but it's not + // an ancestor. + return false; + } + // Any other return code is an error, such as `branch` not being a valid + // name in the repository, so try other name variants. + } + return false; + } + Future _getBranch() async { final io.ProcessResult branchResult = await (await gitDir).runCommand( ['rev-parse', '--abbrev-ref', 'HEAD'], diff --git a/script/tool/lib/src/common/package_state_utils.dart b/script/tool/lib/src/common/package_state_utils.dart index 464dac6c18d6..fbba75c6116f 100644 --- a/script/tool/lib/src/common/package_state_utils.dart +++ b/script/tool/lib/src/common/package_state_utils.dart @@ -171,6 +171,9 @@ Future _isDevChange(List pathComponents, // The top-level "tool" directory is for non-client-facing utility // code, such as test scripts. pathComponents.first == 'tool' || + // The top-level "pigeons" directory is the repo convention for storing + // pigeon input files. + pathComponents.first == 'pigeons' || // Entry point for the 'custom-test' command, which is only for CI and // local testing. pathComponents.first == 'run_tests.sh' || diff --git a/script/tool/lib/src/common/pub_version_finder.dart b/script/tool/lib/src/common/pub_version_finder.dart index 572cb913aa7d..c24ec429f8a3 100644 --- a/script/tool/lib/src/common/pub_version_finder.dart +++ b/script/tool/lib/src/common/pub_version_finder.dart @@ -44,11 +44,13 @@ class PubVersionFinder { result: PubVersionFinderResult.fail, httpResponse: response); } - final List versions = - (json.decode(response.body)['versions'] as List) - .map((final dynamic versionString) => - Version.parse(versionString as String)) - .toList(); + final Map responseBody = + json.decode(response.body) as Map; + final List versions = (responseBody['versions']! as List) + .cast() + .map( + (final String versionString) => Version.parse(versionString)) + .toList(); return PubVersionFinderResponse( versions: versions, diff --git a/script/tool/lib/src/create_all_packages_app_command.dart b/script/tool/lib/src/create_all_packages_app_command.dart index 12ec17da139a..e7719e9f664c 100644 --- a/script/tool/lib/src/create_all_packages_app_command.dart +++ b/script/tool/lib/src/create_all_packages_app_command.dart @@ -138,9 +138,6 @@ class CreateAllPackagesAppCommand extends PackageCommand { if (line.contains('defaultConfig {')) { newGradle.writeln(' multiDexEnabled true'); } else if (line.contains('dependencies {')) { - newGradle.writeln( - " implementation 'com.google.guava:guava:27.0.1-android'\n", - ); // Tests for https://github.com/flutter/flutter/issues/43383 newGradle.writeln( " implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0-rc01'\n", @@ -247,24 +244,23 @@ dev_dependencies:${_pubspecMapString(pubspec.devDependencies)} ###'''; } - String _pubspecMapString(Map values) { + String _pubspecMapString(Map values) { final StringBuffer buffer = StringBuffer(); - for (final MapEntry entry in values.entries) { + for (final MapEntry entry in values.entries) { buffer.writeln(); - if (entry.value is VersionConstraint) { - String value = entry.value.toString(); + final Object? entryValue = entry.value; + if (entryValue is VersionConstraint) { + String value = entryValue.toString(); // Range constraints require quoting. if (value.startsWith('>') || value.startsWith('<')) { value = "'$value'"; } buffer.write(' ${entry.key}: $value'); - } else if (entry.value is SdkDependency) { - final SdkDependency dep = entry.value as SdkDependency; - buffer.write(' ${entry.key}: \n sdk: ${dep.sdk}'); - } else if (entry.value is PathDependency) { - final PathDependency dep = entry.value as PathDependency; - String depPath = dep.path; + } else if (entryValue is SdkDependency) { + buffer.write(' ${entry.key}: \n sdk: ${entryValue.sdk}'); + } else if (entryValue is PathDependency) { + String depPath = entryValue.path; if (path.style == p.Style.windows) { // Posix-style path separators are preferred in pubspec.yaml (and // using a consistent format makes unit testing simpler), so convert. @@ -281,7 +277,7 @@ dev_dependencies:${_pubspecMapString(pubspec.devDependencies)} buffer.write(' ${entry.key}: \n path: $depPath'); } else { throw UnimplementedError( - 'Not available for type: ${entry.value.runtimeType}', + 'Not available for type: ${entryValue.runtimeType}', ); } } diff --git a/script/tool/lib/src/dependabot_check_command.dart b/script/tool/lib/src/dependabot_check_command.dart index 5aa762e916e5..77b44e11b59e 100644 --- a/script/tool/lib/src/dependabot_check_command.dart +++ b/script/tool/lib/src/dependabot_check_command.dart @@ -58,8 +58,9 @@ class DependabotCheckCommand extends PackageLoopingCommand { const String typeKey = 'package-ecosystem'; const String dirKey = 'directory'; _gradleDirs = entries - .where((dynamic entry) => entry[typeKey] == 'gradle') - .map((dynamic entry) => (entry as YamlMap)[dirKey] as String) + .cast() + .where((YamlMap entry) => entry[typeKey] == 'gradle') + .map((YamlMap entry) => entry[dirKey] as String) .toSet(); } diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart index 8198f6d36abd..e4236878658c 100644 --- a/script/tool/lib/src/format_command.dart +++ b/script/tool/lib/src/format_command.dart @@ -191,10 +191,8 @@ class FormatCommand extends PackageCommand { _getPathsWithExtensions(files, {'.dart'}); if (dartFiles.isNotEmpty) { print('Formatting .dart files...'); - // `flutter format` doesn't require the project to actually be a Flutter - // project. - final int exitCode = await _runBatched(flutterCommand, ['format'], - files: dartFiles); + final int exitCode = + await _runBatched('dart', ['format'], files: dartFiles); if (exitCode != 0) { printError('Failed to format Dart files: exit code $exitCode.'); throw ToolExit(_exitFlutterFormatFailed); @@ -298,13 +296,13 @@ class FormatCommand extends PackageCommand { Future> _whichAll(String command) async { try { final io.ProcessResult result = - await processRunner.run('which', ['-a', command]); + await processRunner.run('which', ['-a', command]); if (result.exitCode != 0) { return []; } - final String stdout = result.stdout.trim() as String; + final String stdout = (result.stdout as String).trim(); if (stdout.isEmpty) { return []; } diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index d5ab7f88089e..0083e0cbb8ee 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -21,10 +21,10 @@ import 'fix_command.dart'; import 'format_command.dart'; import 'license_check_command.dart'; import 'lint_android_command.dart'; -import 'lint_podspecs_command.dart'; import 'list_command.dart'; import 'make_deps_path_based_command.dart'; import 'native_test_command.dart'; +import 'podspec_check_command.dart'; import 'publish_check_command.dart'; import 'publish_command.dart'; import 'pubspec_check_command.dart'; @@ -66,7 +66,7 @@ void main(List args) { ..addCommand(FormatCommand(packagesDir)) ..addCommand(LicenseCheckCommand(packagesDir)) ..addCommand(LintAndroidCommand(packagesDir)) - ..addCommand(LintPodspecsCommand(packagesDir)) + ..addCommand(PodspecCheckCommand(packagesDir)) ..addCommand(ListCommand(packagesDir)) ..addCommand(NativeTestCommand(packagesDir)) ..addCommand(MakeDepsPathBasedCommand(packagesDir)) diff --git a/script/tool/lib/src/lint_podspecs_command.dart b/script/tool/lib/src/podspec_check_command.dart similarity index 54% rename from script/tool/lib/src/lint_podspecs_command.dart rename to script/tool/lib/src/podspec_check_command.dart index 198dd9472115..4cda7210a8ef 100644 --- a/script/tool/lib/src/lint_podspecs_command.dart +++ b/script/tool/lib/src/podspec_check_command.dart @@ -6,7 +6,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:file/file.dart'; -import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'common/core.dart'; @@ -20,23 +19,24 @@ const int _exitPodNotInstalled = 3; /// Lint the CocoaPod podspecs and run unit tests. /// /// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_lint. -class LintPodspecsCommand extends PackageLoopingCommand { +class PodspecCheckCommand extends PackageLoopingCommand { /// Creates an instance of the linter command. - LintPodspecsCommand( + PodspecCheckCommand( Directory packagesDir, { ProcessRunner processRunner = const ProcessRunner(), Platform platform = const LocalPlatform(), }) : super(packagesDir, processRunner: processRunner, platform: platform); @override - final String name = 'podspecs'; + final String name = 'podspec-check'; @override - List get aliases => ['podspec']; + List get aliases => ['podspec', 'podspecs']; @override final String description = - 'Runs "pod lib lint" on all iOS and macOS plugin podspecs.\n\n' + 'Runs "pod lib lint" on all iOS and macOS plugin podspecs, as well as ' + 'making sure the podspecs follow repository standards.\n\n' 'This command requires "pod" and "flutter" to be in your path. Runs on macOS only.'; @override @@ -69,9 +69,32 @@ class LintPodspecsCommand extends PackageLoopingCommand { for (final File podspec in podspecs) { if (!await _lintPodspec(podspec)) { - errors.add(p.basename(podspec.path)); + errors.add(podspec.basename); } } + + if (await _hasIOSSwiftCode(package)) { + print('iOS Swift code found, checking for search paths settings...'); + for (final File podspec in podspecs) { + if (_isPodspecMissingSearchPaths(podspec)) { + const String workaroundBlock = r''' + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } +'''; + final String path = + getRelativePosixPath(podspec, from: package.directory); + printError('$path is missing seach path configuration. Any iOS ' + 'plugin implementation that contains Swift implementation code ' + 'needs to contain the following:\n\n' + '$workaroundBlock\n' + 'For more details, see https://github.com/flutter/flutter/issues/118418.'); + errors.add(podspec.basename); + } + } + } + return errors.isEmpty ? PackageResult.success() : PackageResult.fail(errors); @@ -92,7 +115,7 @@ class LintPodspecsCommand extends PackageLoopingCommand { // Do not run the static analyzer on plugins with known analyzer issues. final String podspecPath = podspec.path; - final String podspecBasename = p.basename(podspecPath); + final String podspecBasename = podspec.basename; print('Linting $podspecBasename'); // Lint plugin as framework (use_frameworks!). @@ -126,4 +149,46 @@ class LintPodspecsCommand extends PackageLoopingCommand { return processRunner.run('pod', arguments, workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8); } + + /// Returns true if there is any iOS plugin implementation code written in + /// Swift. + Future _hasIOSSwiftCode(RepositoryPackage package) async { + return getFilesForPackage(package).any((File entity) { + final String relativePath = + getRelativePosixPath(entity, from: package.directory); + // Ignore example code. + if (relativePath.startsWith('example/')) { + return false; + } + final String filePath = entity.path; + return path.extension(filePath) == '.swift'; + }); + } + + /// Returns true if [podspec] could apply to iOS, but does not have the + /// workaround for search paths that makes Swift plugins build correctly in + /// Objective-C applications. See + /// https://github.com/flutter/flutter/issues/118418 for context and details. + /// + /// This does not check that the plugin has Swift code, and thus whether the + /// workaround is needed, only whether or not it is there. + bool _isPodspecMissingSearchPaths(File podspec) { + final String directory = podspec.parent.basename; + // All macOS Flutter apps are Swift, so macOS-only podspecs don't need the + // workaround. If it's anywhere other than macos/, err or the side of + // assuming it's required. + if (directory == 'macos') { + return false; + } + + // This errs on the side of being too strict, to minimize the chance of + // accidental incorrect configuration. If we ever need more flexibility + // due to a false negative we can adjust this as necessary. + final RegExp workaround = RegExp(r''' +\s*s\.(?:ios\.)?xcconfig = {[^}]* +\s*'LIBRARY_SEARCH_PATHS' => '\$\(TOOLCHAIN_DIR\)/usr/lib/swift/\$\(PLATFORM_NAME\)/ \$\(SDKROOT\)/usr/lib/swift', +\s*'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',[^}]* +\s*}''', dotAll: true); + return !workaround.hasMatch(podspec.readAsStringSync()); + } } diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart index 79ef1e1d3e5e..aefa316a41f6 100644 --- a/script/tool/lib/src/pubspec_check_command.dart +++ b/script/tool/lib/src/pubspec_check_command.dart @@ -5,6 +5,7 @@ import 'package:file/file.dart'; import 'package:git/git.dart'; import 'package:platform/platform.dart'; +import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; import 'common/core.dart'; @@ -29,7 +30,23 @@ class PubspecCheckCommand extends PackageLoopingCommand { processRunner: processRunner, platform: platform, gitDir: gitDir, - ); + ) { + argParser.addOption( + _minMinDartVersionFlag, + help: + 'The minimum Dart version to allow as the minimum SDK constraint.\n\n' + 'This is only enforced for non-Flutter packages; Flutter packages ' + 'use --$_minMinFlutterVersionFlag', + ); + argParser.addOption( + _minMinFlutterVersionFlag, + help: + 'The minimum Flutter version to allow as the minimum SDK constraint.', + ); + } + + static const String _minMinDartVersionFlag = 'min-min-dart-version'; + static const String _minMinFlutterVersionFlag = 'min-min-flutter-version'; // Section order for plugins. Because the 'flutter' section is critical // information for plugins, and usually small, it goes near the top unlike in @@ -100,6 +117,24 @@ class PubspecCheckCommand extends PackageLoopingCommand { printError('$listIndentation${sectionOrder.join('\n$listIndentation')}'); } + final String minMinDartVersionString = getStringArg(_minMinDartVersionFlag); + final String minMinFlutterVersionString = + getStringArg(_minMinFlutterVersionFlag); + final String? minVersionError = _checkForMinimumVersionError( + pubspec, + package, + minMinDartVersion: minMinDartVersionString.isEmpty + ? null + : Version.parse(minMinDartVersionString), + minMinFlutterVersion: minMinFlutterVersionString.isEmpty + ? null + : Version.parse(minMinFlutterVersionString), + ); + if (minVersionError != null) { + printError('$indentation$minVersionError'); + passing = false; + } + if (isPlugin) { final String? implementsError = _checkForImplementsError(pubspec, package: package); @@ -244,8 +279,8 @@ class PubspecCheckCommand extends PackageLoopingCommand { required RepositoryPackage package, }) { if (_isImplementationPackage(package)) { - final String? implements = - pubspec.flutter!['plugin']!['implements'] as String?; + final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; + final String? implements = pluginSection['implements'] as String?; final String expectedImplements = package.directory.parent.basename; if (implements == null) { return 'Missing "implements: $expectedImplements" in "plugin" section.'; @@ -265,19 +300,20 @@ class PubspecCheckCommand extends PackageLoopingCommand { Pubspec pubspec, { required RepositoryPackage package, }) { - final dynamic platformsEntry = pubspec.flutter!['plugin']!['platforms']; - if (platformsEntry == null) { + final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; + final YamlMap? platforms = pluginSection['platforms'] as YamlMap?; + if (platforms == null) { logWarning('Does not implement any platforms'); return null; } - final YamlMap platforms = platformsEntry as YamlMap; final String packageName = package.directory.basename; // Validate that the default_package entries look correct (e.g., no typos). final Set defaultPackages = {}; - for (final MapEntry platformEntry in platforms.entries) { + for (final MapEntry platformEntry in platforms.entries) { + final YamlMap platformDetails = platformEntry.value! as YamlMap; final String? defaultPackage = - platformEntry.value['default_package'] as String?; + platformDetails['default_package'] as String?; if (defaultPackage != null) { defaultPackages.add(defaultPackage); if (!defaultPackage.startsWith('${packageName}_')) { @@ -319,4 +355,43 @@ class PubspecCheckCommand extends PackageLoopingCommand { final String suffix = packageName.substring(parentName.length); return !nonImplementationSuffixes.contains(suffix); } + + /// Validates that a Flutter package has a minimum SDK version constraint of + /// at least [minMinFlutterVersion] (if provided), or that a non-Flutter + /// package has a minimum SDK version constraint of [minMinDartVersion] + /// (if provided). + /// + /// Returns an error string if validation fails. + String? _checkForMinimumVersionError( + Pubspec pubspec, + RepositoryPackage package, { + Version? minMinDartVersion, + Version? minMinFlutterVersion, + }) { + final VersionConstraint? dartConstraint = pubspec.environment?['sdk']; + final VersionConstraint? flutterConstraint = + pubspec.environment?['flutter']; + + if (flutterConstraint != null) { + // Validate Flutter packages against the Flutter requirement. + if (minMinFlutterVersion != null) { + final Version? constraintMin = + flutterConstraint is VersionRange ? flutterConstraint.min : null; + if ((constraintMin ?? Version(0, 0, 0)) < minMinFlutterVersion) { + return 'Minimum allowed Flutter version $constraintMin is less than $minMinFlutterVersion'; + } + } + } else { + // Validate non-Flutter packages against the Dart requirement. + if (minMinDartVersion != null) { + final Version? constraintMin = + dartConstraint is VersionRange ? dartConstraint.min : null; + if ((constraintMin ?? Version(0, 0, 0)) < minMinDartVersion) { + return 'Minimum allowed Dart version $constraintMin is less than $minMinDartVersion'; + } + } + } + + return null; + } } diff --git a/script/tool/lib/src/readme_check_command.dart b/script/tool/lib/src/readme_check_command.dart index e3fbc7bc454d..cbbb8b835a13 100644 --- a/script/tool/lib/src/readme_check_command.dart +++ b/script/tool/lib/src/readme_check_command.dart @@ -234,7 +234,8 @@ class ReadmeCheckCommand extends PackageLoopingCommand { } // Validate that the supported OS lists match. - final dynamic platformsEntry = pubspec.flutter!['plugin']!['platforms']; + final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; + final dynamic platformsEntry = pluginSection['platforms']; if (platformsEntry == null) { logWarning('Plugin not support any platforms'); return null; diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart index 5a59104d4e7f..e65bed846cbc 100644 --- a/script/tool/lib/src/update_excerpts_command.dart +++ b/script/tool/lib/src/update_excerpts_command.dart @@ -206,20 +206,28 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { .renameSync(package.pubspecFile.path); } - /// Checks the git state, returning an error string unless nothing has + /// Checks the git state, returning an error string if any .md files have /// changed. Future _validateRepositoryState() async { - final io.ProcessResult modifiedFiles = await processRunner.run( + final io.ProcessResult checkFiles = await processRunner.run( 'git', ['ls-files', '--modified'], workingDir: packagesDir, logOnError: true, ); - if (modifiedFiles.exitCode != 0) { + if (checkFiles.exitCode != 0) { return 'Unable to determine local file state'; } - final String stdout = modifiedFiles.stdout as String; - return stdout.trim().isEmpty ? null : 'Snippets are out of sync'; + final String stdout = checkFiles.stdout as String; + final List changedFiles = stdout.trim().split('\n'); + final Iterable changedMDFiles = + changedFiles.where((String filePath) => filePath.endsWith('.md')); + if (changedMDFiles.isNotEmpty) { + return 'Snippets are out of sync in the following files: ' + '${changedMDFiles.join(', ')}'; + } + + return null; } } diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index ea20364d51d5..52d23b8f72a3 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool -version: 0.13.2 +version: 0.13.4+2 dependencies: args: ^2.1.0 diff --git a/script/tool/test/common/git_version_finder_test.dart b/script/tool/test/common/git_version_finder_test.dart index d5a5dd4fe876..538b72a90021 100644 --- a/script/tool/test/common/git_version_finder_test.dart +++ b/script/tool/test/common/git_version_finder_test.dart @@ -22,12 +22,14 @@ void main() { gitDir = MockGitDir(); when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) .thenAnswer((Invocation invocation) { - gitDirCommands.add(invocation.positionalArguments[0] as List?); + final List arguments = + invocation.positionalArguments[0]! as List; + gitDirCommands.add(arguments); final MockProcessResult mockProcessResult = MockProcessResult(); - if (invocation.positionalArguments[0][0] == 'diff') { + if (arguments[0] == 'diff') { when(mockProcessResult.stdout as String?) .thenReturn(gitDiffResponse); - } else if (invocation.positionalArguments[0][0] == 'merge-base') { + } else if (arguments[0] == 'merge-base') { when(mockProcessResult.stdout as String?) .thenReturn(mergeBaseResponse); } diff --git a/script/tool/test/common/package_command_test.dart b/script/tool/test/common/package_command_test.dart index aa0a20253955..3620f8fd63a9 100644 --- a/script/tool/test/common/package_command_test.dart +++ b/script/tool/test/common/package_command_test.dart @@ -778,7 +778,8 @@ packages/b_package/lib/src/foo.dart MockProcess(stdout: 'a-branch'), ]; processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(stdout: 'abc123'), + MockProcess(exitCode: 1), // --is-ancestor check + MockProcess(stdout: 'abc123'), // finding merge base ]; final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); @@ -791,6 +792,7 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ + contains('--packages-for-branch: running on branch "a-branch"'), contains( 'Running for all packages that have diffs relative to "abc123"'), ])); @@ -822,8 +824,85 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ - contains('--packages-for-branch: running on default branch; ' - 'using parent commit as the diff base'), + contains('--packages-for-branch: running on default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), + contains( + 'Running for all packages that have diffs relative to "HEAD~"'), + ])); + // Ensure that it's diffing against the prior commit. + expect( + processRunner.recordedCalls, + contains( + const ProcessCall( + 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), + )); + }); + + test( + 'only tests changed packages relative to the previous commit if ' + 'running on a specific hash from main', () async { + processRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess(stdout: 'packages/plugin1/plugin1.dart'), + ]; + processRunner.mockProcessesForExecutable['git-rev-parse'] = [ + MockProcess(stdout: 'HEAD'), + ]; + final RepositoryPackage plugin1 = + createFakePlugin('plugin1', packagesDir); + createFakePlugin('plugin2', packagesDir); + + final List output = await runCapturingPrint( + runner, ['sample', '--packages-for-branch']); + + expect(command.plugins, unorderedEquals([plugin1.path])); + expect( + output, + containsAllInOrder([ + contains( + '--packages-for-branch: running on a commit from default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), + contains( + 'Running for all packages that have diffs relative to "HEAD~"'), + ])); + // Ensure that it's diffing against the prior commit. + expect( + processRunner.recordedCalls, + contains( + const ProcessCall( + 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), + )); + }); + + test( + 'only tests changed packages relative to the previous commit if ' + 'running on a specific hash from origin/main', () async { + processRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess(stdout: 'packages/plugin1/plugin1.dart'), + ]; + processRunner.mockProcessesForExecutable['git-rev-parse'] = [ + MockProcess(stdout: 'HEAD'), + ]; + processRunner.mockProcessesForExecutable['git-merge-base'] = [ + MockProcess(exitCode: 128), // Fail with a non-1 exit code for 'main' + MockProcess(), // Succeed for the variant. + ]; + final RepositoryPackage plugin1 = + createFakePlugin('plugin1', packagesDir); + createFakePlugin('plugin2', packagesDir); + + final List output = await runCapturingPrint( + runner, ['sample', '--packages-for-branch']); + + expect(command.plugins, unorderedEquals([plugin1.path])); + expect( + output, + containsAllInOrder([ + contains( + '--packages-for-branch: running on a commit from default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), contains( 'Running for all packages that have diffs relative to "HEAD~"'), ])); @@ -836,7 +915,9 @@ packages/b_package/lib/src/foo.dart )); }); - test('tests all packages on master', () async { + test( + 'only tests changed packages relative to the previous commit on master', + () async { processRunner.mockProcessesForExecutable['git-diff'] = [ MockProcess(stdout: 'packages/plugin1/plugin1.dart'), ]; @@ -854,8 +935,9 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ - contains('--packages-for-branch: running on default branch; ' - 'using parent commit as the diff base'), + contains('--packages-for-branch: running on default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), contains( 'Running for all packages that have diffs relative to "HEAD~"'), ])); @@ -887,7 +969,7 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ - contains('Unabled to determine branch'), + contains('Unable to determine branch'), ])); }); }); diff --git a/script/tool/test/common/package_looping_command_test.dart b/script/tool/test/common/package_looping_command_test.dart index f90d58e12270..34f346c62fe7 100644 --- a/script/tool/test/common/package_looping_command_test.dart +++ b/script/tool/test/common/package_looping_command_test.dart @@ -110,8 +110,10 @@ void main() { final MockGitDir gitDir = MockGitDir(); when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) .thenAnswer((Invocation invocation) { + final List arguments = + invocation.positionalArguments[0]! as List; final MockProcessResult mockProcessResult = MockProcessResult(); - if (invocation.positionalArguments[0][0] == 'diff') { + if (arguments[0] == 'diff') { when(mockProcessResult.stdout as String?) .thenReturn(gitDiffResponse); } diff --git a/script/tool/test/common/package_state_utils_test.dart b/script/tool/test/common/package_state_utils_test.dart index 86029cdf73a8..9b6429a084ce 100644 --- a/script/tool/test/common/package_state_utils_test.dart +++ b/script/tool/test/common/package_state_utils_test.dart @@ -68,6 +68,8 @@ void main() { 'packages/a_plugin/example/android/src/androidTest/foo/bar/FooTest.java', 'packages/a_plugin/example/ios/RunnerTests/Foo.m', 'packages/a_plugin/example/ios/RunnerUITests/info.plist', + // Pigeon input. + 'packages/a_plugin/pigeons/messages.dart', // Test scripts. 'packages/a_plugin/run_tests.sh', // Tools. diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart index 1aadafbd3d82..634a996bccc6 100644 --- a/script/tool/test/format_command_test.dart +++ b/script/tool/test/format_command_test.dart @@ -98,7 +98,7 @@ void main() { processRunner.recordedCalls, orderedEquals([ ProcessCall( - getFlutterCommand(mockPlatform), + 'dart', ['format', ...getPackagesDirRelativePaths(plugin, files)], packagesDir.path), ])); @@ -132,7 +132,7 @@ void main() { processRunner.recordedCalls, orderedEquals([ ProcessCall( - getFlutterCommand(mockPlatform), + 'dart', [ 'format', ...getPackagesDirRelativePaths(plugin, formattedFiles) @@ -141,7 +141,7 @@ void main() { ])); }); - test('fails if flutter format fails', () async { + test('fails if dart format fails', () async { const List files = [ 'lib/a.dart', 'lib/src/b.dart', @@ -149,8 +149,9 @@ void main() { ]; createFakePlugin('a_plugin', packagesDir, extraFiles: files); - processRunner.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [MockProcess(exitCode: 1)]; + processRunner.mockProcessesForExecutable['dart'] = [ + MockProcess(exitCode: 1) + ]; Error? commandError; final List output = await runCapturingPrint( runner, ['format'], errorHandler: (Error e) { @@ -465,7 +466,7 @@ void main() { ], packagesDir.path), ProcessCall( - getFlutterCommand(mockPlatform), + 'dart', [ 'format', ...getPackagesDirRelativePaths(plugin, dartFiles) @@ -594,7 +595,7 @@ void main() { processRunner.recordedCalls, contains( ProcessCall( - getFlutterCommand(mockPlatform), + 'dart', [ 'format', '$pluginName\\$extraFile', @@ -651,7 +652,7 @@ void main() { processRunner.recordedCalls, contains( ProcessCall( - getFlutterCommand(mockPlatform), + 'dart', [ 'format', '$pluginName/$extraFile', diff --git a/script/tool/test/lint_podspecs_command_test.dart b/script/tool/test/lint_podspecs_command_test.dart deleted file mode 100644 index 097bcff338a5..000000000000 --- a/script/tool/test/lint_podspecs_command_test.dart +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/lint_podspecs_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('$LintPodspecsCommand', () { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late MockPlatform mockPlatform; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - mockPlatform = MockPlatform(isMacOS: true); - processRunner = RecordingProcessRunner(); - final LintPodspecsCommand command = LintPodspecsCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = - CommandRunner('podspec_test', 'Test for $LintPodspecsCommand'); - runner.addCommand(command); - }); - - test('only runs on macOS', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - mockPlatform.isMacOS = false; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - processRunner.recordedCalls, - equals([]), - ); - - expect( - output, - containsAllInOrder( - [contains('only supported on macOS')], - )); - }); - - test('runs pod lib lint on a podspec', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin1', - packagesDir, - extraFiles: [ - 'ios/plugin1.podspec', - 'bogus.dart', // Ignore non-podspecs. - ], - ); - - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(stdout: 'Foo', stderr: 'Bar'), - MockProcess(), - ]; - - final List output = - await runCapturingPrint(runner, ['podspecs']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('which', const ['pod'], packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - '--use-libraries' - ], - packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - ], - packagesDir.path), - ]), - ); - - expect(output, contains('Linting plugin1.podspec')); - expect(output, contains('Foo')); - expect(output, contains('Bar')); - }); - - test('fails if pod is missing', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - - // Simulate failure from `which pod`. - processRunner.mockProcessesForExecutable['which'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('Unable to find "pod". Make sure it is in your path.'), - ], - )); - }); - - test('fails if linting as a framework fails', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - - // Simulate failure from `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('fails if linting as a static library fails', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - - // Simulate failure from the second call to `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(), - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspecs'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('skips when there are no podspecs', () async { - createFakePlugin('plugin1', packagesDir); - - final List output = - await runCapturingPrint(runner, ['podspecs']); - - expect( - output, - containsAllInOrder( - [contains('SKIPPING: No podspecs.')], - )); - }); - }); -} diff --git a/script/tool/test/podspec_check_command_test.dart b/script/tool/test/podspec_check_command_test.dart new file mode 100644 index 000000000000..c31ffd46a4b7 --- /dev/null +++ b/script/tool/test/podspec_check_command_test.dart @@ -0,0 +1,428 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io' as io; + +import 'package:args/command_runner.dart'; +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_plugin_tools/src/common/core.dart'; +import 'package:flutter_plugin_tools/src/podspec_check_command.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'util.dart'; + +/// Adds a fake podspec to [plugin]'s [platform] directory. +/// +/// If [includeSwiftWorkaround] is set, the xcconfig additions to make Swift +/// libraries work in apps that have no Swift will be included. If +/// [scopeSwiftWorkaround] is set, it will be specific to the iOS configuration. +void _writeFakePodspec(RepositoryPackage plugin, String platform, + {bool includeSwiftWorkaround = false, bool scopeSwiftWorkaround = false}) { + final String pluginName = plugin.directory.basename; + final File file = plugin.directory + .childDirectory(platform) + .childFile('$pluginName.podspec'); + final String swiftWorkaround = includeSwiftWorkaround + ? ''' + s.${scopeSwiftWorkaround ? 'ios.' : ''}xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '\$(TOOLCHAIN_DIR)/usr/lib/swift/\$(PLATFORM_NAME)/ \$(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + } +''' + : ''; + file.createSync(recursive: true); + file.writeAsStringSync(''' +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'shared_preferences_foundation' + s.version = '0.0.1' + s.summary = 'iOS and macOS implementation of the shared_preferences plugin.' + s.description = <<-DESC +Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' } + s.source_files = 'Classes/**/*' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + $swiftWorkaround + s.swift_version = '5.0' + +end +'''); +} + +void main() { + group('PodspecCheckCommand', () { + FileSystem fileSystem; + late Directory packagesDir; + late CommandRunner runner; + late MockPlatform mockPlatform; + late RecordingProcessRunner processRunner; + + setUp(() { + fileSystem = MemoryFileSystem(); + packagesDir = createPackagesDirectory(fileSystem: fileSystem); + + mockPlatform = MockPlatform(isMacOS: true); + processRunner = RecordingProcessRunner(); + final PodspecCheckCommand command = PodspecCheckCommand( + packagesDir, + processRunner: processRunner, + platform: mockPlatform, + ); + + runner = + CommandRunner('podspec_test', 'Test for $PodspecCheckCommand'); + runner.addCommand(command); + }); + + test('only runs on macOS', () async { + createFakePlugin('plugin1', packagesDir, + extraFiles: ['plugin1.podspec']); + mockPlatform.isMacOS = false; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + processRunner.recordedCalls, + equals([]), + ); + + expect( + output, + containsAllInOrder( + [contains('only supported on macOS')], + )); + }); + + test('runs pod lib lint on a podspec', () async { + final RepositoryPackage plugin = createFakePlugin( + 'plugin1', + packagesDir, + extraFiles: [ + 'bogus.dart', // Ignore non-podspecs. + ], + ); + _writeFakePodspec(plugin, 'ios'); + + processRunner.mockProcessesForExecutable['pod'] = [ + MockProcess(stdout: 'Foo', stderr: 'Bar'), + MockProcess(), + ]; + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall('which', const ['pod'], packagesDir.path), + ProcessCall( + 'pod', + [ + 'lib', + 'lint', + plugin + .platformDirectory(FlutterPlatform.ios) + .childFile('plugin1.podspec') + .path, + '--configuration=Debug', + '--skip-tests', + '--use-modular-headers', + '--use-libraries' + ], + packagesDir.path), + ProcessCall( + 'pod', + [ + 'lib', + 'lint', + plugin + .platformDirectory(FlutterPlatform.ios) + .childFile('plugin1.podspec') + .path, + '--configuration=Debug', + '--skip-tests', + '--use-modular-headers', + ], + packagesDir.path), + ]), + ); + + expect(output, contains('Linting plugin1.podspec')); + expect(output, contains('Foo')); + expect(output, contains('Bar')); + }); + + test('fails if pod is missing', () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); + _writeFakePodspec(plugin, 'ios'); + + // Simulate failure from `which pod`. + processRunner.mockProcessesForExecutable['which'] = [ + MockProcess(exitCode: 1), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains('Unable to find "pod". Make sure it is in your path.'), + ], + )); + }); + + test('fails if linting as a framework fails', () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); + _writeFakePodspec(plugin, 'ios'); + + // Simulate failure from `pod`. + processRunner.mockProcessesForExecutable['pod'] = [ + MockProcess(exitCode: 1), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains('The following packages had errors:'), + contains('plugin1:\n' + ' plugin1.podspec') + ], + )); + }); + + test('fails if linting as a static library fails', () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); + _writeFakePodspec(plugin, 'ios'); + + // Simulate failure from the second call to `pod`. + processRunner.mockProcessesForExecutable['pod'] = [ + MockProcess(), + MockProcess(exitCode: 1), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains('The following packages had errors:'), + contains('plugin1:\n' + ' plugin1.podspec') + ], + )); + }); + + test('fails if an iOS Swift plugin is missing the search paths workaround', + () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: ['ios/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'ios'); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains(r''' + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + }'''), + contains('The following packages had errors:'), + contains('plugin1:\n' + ' plugin1.podspec') + ], + )); + }); + + test( + 'fails if a shared-source Swift plugin is missing the search paths workaround', + () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: ['darwin/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'darwin'); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['podspec-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + + expect( + output, + containsAllInOrder( + [ + contains(r''' + s.xcconfig = { + 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', + 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', + }'''), + contains('The following packages had errors:'), + contains('plugin1:\n' + ' plugin1.podspec') + ], + )); + }); + + test('does not require the search paths workaround for macOS plugins', + () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: ['macos/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'macos'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('does not require the search paths workaround for ObjC iOS plugins', + () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: [ + 'ios/Classes/SomeObjC.h', + 'ios/Classes/SomeObjC.m' + ]); + _writeFakePodspec(plugin, 'ios'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('passes if the search paths workaround is present', () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: ['ios/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'ios', includeSwiftWorkaround: true); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('passes if the search paths workaround is present for iOS only', + () async { + final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, + extraFiles: ['ios/Classes/SomeSwift.swift']); + _writeFakePodspec(plugin, 'ios', + includeSwiftWorkaround: true, scopeSwiftWorkaround: true); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('does not require the search paths workaround for Swift example code', + () async { + final RepositoryPackage plugin = + createFakePlugin('plugin1', packagesDir, extraFiles: [ + 'ios/Classes/SomeObjC.h', + 'ios/Classes/SomeObjC.m', + 'example/ios/Runner/AppDelegate.swift', + ]); + _writeFakePodspec(plugin, 'ios'); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [ + contains('Ran for 1 package(s)'), + ], + )); + }); + + test('skips when there are no podspecs', () async { + createFakePlugin('plugin1', packagesDir); + + final List output = + await runCapturingPrint(runner, ['podspec-check']); + + expect( + output, + containsAllInOrder( + [contains('SKIPPING: No podspecs.')], + )); + }); + }); +} diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart index 2c254ca94984..7a9c0cec7cbc 100644 --- a/script/tool/test/pubspec_check_command_test.dart +++ b/script/tool/test/pubspec_check_command_test.dart @@ -60,12 +60,16 @@ ${publishable ? '' : "publish_to: 'none'"} '''; } -String _environmentSection() { - return ''' -environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" -'''; +String _environmentSection({ + String dartConstraint = '>=2.12.0 <3.0.0', + String? flutterConstraint = '>=2.0.0', +}) { + return [ + 'environment:', + ' sdk: "$dartConstraint"', + if (flutterConstraint != null) ' flutter: "$flutterConstraint"', + '', + ].join('\n'); } String _flutterSection({ @@ -931,6 +935,163 @@ ${_devDependenciesSection()} ]), ); }); + + test('fails when a Flutter package has a too-low minimum Flutter version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(flutterConstraint: '>=2.10.0')} +${_dependenciesSection()} +'''); + + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'pubspec-check', + '--min-min-flutter-version', + '3.0.0' + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Minimum allowed Flutter version 2.10.0 is less than 3.0.0'), + ]), + ); + }); + + test( + 'passes when a Flutter package requires exactly the minimum Flutter version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(flutterConstraint: '>=3.0.0')} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-flutter-version', '3.0.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); + + test( + 'passes when a Flutter package requires a higher minimum Flutter version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(flutterConstraint: '>=3.3.0')} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-flutter-version', '3.0.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); + + test('fails when a non-Flutter package has a too-low minimum Dart version', + () async { + final RepositoryPackage package = + createFakePackage('a_package', packagesDir, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(dartConstraint: '>=2.14.0 <3.0.0', flutterConstraint: null)} +${_dependenciesSection()} +'''); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['pubspec-check', '--min-min-dart-version', '2.17.0'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Minimum allowed Dart version 2.14.0 is less than 2.17.0'), + ]), + ); + }); + + test( + 'passes when a non-Flutter package requires exactly the minimum Dart version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(dartConstraint: '>=2.17.0 <3.0.0', flutterConstraint: null)} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-dart-version', '2.17.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); + + test( + 'passes when a non-Flutter package requires a higher minimum Dart version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(dartConstraint: '>=2.18.0 <3.0.0', flutterConstraint: null)} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-dart-version', '2.17.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); }); group('test pubspec_check_command on Windows', () { diff --git a/script/tool/test/update_excerpts_command_test.dart b/script/tool/test/update_excerpts_command_test.dart index 79f53d8779bb..5a2f0f340414 100644 --- a/script/tool/test/update_excerpts_command_test.dart +++ b/script/tool/test/update_excerpts_command_test.dart @@ -232,11 +232,11 @@ void main() { ])); }); - test('fails if files are changed with --fail-on-change', () async { + test('fails if READMEs are changed with --fail-on-change', () async { createFakePlugin('a_plugin', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); - const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc'; + const String changedFilePath = 'packages/a_plugin/README.md'; processRunner.mockProcessesForExecutable['git'] = [ MockProcess(stdout: changedFilePath), ]; @@ -253,6 +253,27 @@ void main() { output, containsAllInOrder([ contains('README.md is out of sync with its source excerpts'), + contains('Snippets are out of sync in the following files: ' + 'packages/a_plugin/README.md'), + ])); + }); + + test('passes if unrelated files are changed with --fail-on-change', () async { + createFakePlugin('a_plugin', packagesDir, + extraFiles: [kReadmeExcerptConfigPath]); + + const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; + processRunner.mockProcessesForExecutable['git'] = [ + MockProcess(stdout: changedFilePath), + ]; + + final List output = await runCapturingPrint( + runner, ['update-excerpts', '--fail-on-change']); + + expect( + output, + containsAllInOrder([ + contains('Ran for 1 package(s)'), ])); }); diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart index a8cb527d9238..913242b6ea69 100644 --- a/script/tool/test/util.dart +++ b/script/tool/test/util.dart @@ -453,7 +453,7 @@ class ProcessCall { final String? workingDir; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return other is ProcessCall && executable == other.executable && listsEqual(args, other.args) && From 0d70eb561832cb0d408700d3e6a331b280219f51 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Sat, 4 Feb 2023 13:39:47 +0100 Subject: [PATCH 08/11] [local_auth] Add Android theme description to changelog --- packages/local_auth/local_auth/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index d5ad7aa9a28a..a73f3e24b845 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum Flutter version to 3.0. +* Updates documentation for Android version 8 and below theme compatibility. ## 2.1.3 From 7964addfd365cd14ba58fd52cc9de0e5f785919a Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Sat, 4 Feb 2023 13:49:10 +0100 Subject: [PATCH 09/11] [local_auth] Set last version (2.1.3) --- packages/local_auth/local_auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index c2d3a007d10b..769de34b2bb6 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 2.1.4 +version: 2.1.3 environment: sdk: ">=2.14.0 <3.0.0" From 3feee168bab776f0d67af3fca2c560406cef5bdd Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Tue, 7 Feb 2023 17:06:31 +0100 Subject: [PATCH 10/11] [local_auth] Upgrade version to 2.1.4 --- packages/local_auth/local_auth/CHANGELOG.md | 2 +- packages/local_auth/local_auth/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index a73f3e24b845..0028704b34b1 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,4 +1,4 @@ -## NEXT +## 2.1.4 * Updates minimum Flutter version to 3.0. * Updates documentation for Android version 8 and below theme compatibility. diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index 769de34b2bb6..c2d3a007d10b 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 2.1.3 +version: 2.1.4 environment: sdk: ">=2.14.0 <3.0.0" From 443aa37f2b60c0a70e0bd1cf2a2c54c82528d502 Mon Sep 17 00:00:00 2001 From: Abel1027 Date: Tue, 7 Feb 2023 17:29:53 +0100 Subject: [PATCH 11/11] [DOC][local_auth] Amend android theme compatibility suggestion --- packages/local_auth/local_auth/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index 0c2a5842df29..8abf583b9dd4 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -254,14 +254,11 @@ This will return an error if there was no hardware available. #### Android theme -You need to update the `LaunchTheme` parent style with a valid `Theme.AppCompat` -theme to be compatible with **Android version 8 and below**, otherwise the app -crashes for those versions. For example, use `Theme.AppCompat.DayNight` to +Your `LaunchTheme`'s parent must be a valid `Theme.AppCompat` theme to prevent +crashes on Android 8 and below. For example, use `Theme.AppCompat.DayNight` to enable light/dark modes for the biometric dialog. To do that go to `android/app/src/main/res/values/styles.xml` and look for the style with name -`LaunchTheme` (Notice that `LaunchTheme` must be referenced in the -`AndroidManifest.xml` file to apply the changes made in `styles.xml`). -Then change the parent for that style as follows: +`LaunchTheme`. Then change the parent for that style as follows: ```xml ... @@ -274,7 +271,8 @@ Then change the parent for that style as follows: ... ``` -If you don't have a `styles.xml` file for your Android project you can set up the Android theme directly in `android/app/src/main/AndroidManifest.xml`: +If you don't have a `styles.xml` file for your Android project you can set up +the Android theme directly in `android/app/src/main/AndroidManifest.xml`: ```xml ...